<template>
  <div class="app-container">
    <el-row :gutter="10" class="mb8">
      <!-- <AssetPathWizardOnly @refresh="refreshByParent" /> -->
      <el-button type="primary" @click="show = true">创建系统</el-button>
      <SystemCreateDialog v-model="show" :stayOpenAfterCreate="false" @success="reload" />
      <!-- <ExcelUploader
          action="/sbtz/sbtz/excel"
          template-url="/sbtz/sbtz/template"
          template-name="设备导入模板.xlsx"
          :data="{ beType: currentBeType }"         
          @success="onImported"
        /> -->
        
      <!-- <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          :disabled="selectedIds.length === 0"
          @click="confirmBatchDelete"
        >
          批量删除（{{ selectedIds.length }}）
        </el-button>
      </el-col> -->
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="Download"
          @click="handleExport"
          v-hasPermi="['sbtz:sbtz:export']"
          >导出</el-button
        >
      </el-col>
    </el-row>

    <!-- 母设备表格 -->
    <el-table
      :data="motherRows"
      border
      stripe
      @row-dblclick="openChildrenDialog"
      :row-class-name="rowClassName"
      @sort-change="mainSortChange"
      v-loading="dblBusy"
      height="680"
      element-loading-text="正在加载子层数据…"
      element-loading-background="rgba(255,255,255,.6)"
    >
      <!-- <el-table-column type="expand" width="30" fixed="left">
        <template #default="{ row }">
          <el-table
            :data="row.spareInfos"
            size="small"
            border
            style="width: 25%"
          >
            <el-table-column prop="name" label="备件名称" min-width="160" />
            <el-table-column
              prop="code"
              label="编码"
              width="140"
              show-overflow-tooltip
            />
            <el-table-column
              prop="stockTotal"
              label="总库存"
              width="90"
              align="center"
            />
            <el-table-column
              prop="stockAvailable"
              label="当前库存"
              width="100"
              align="center"
            />
            <el-table-column
              prop="qtyNeed"
              label="需求数量"
              width="90"
              align="center"
            />
            <el-table-column label="使用数量" width="90" align="center">
              <template #default="scope">
                {{ scope.row.useNumber }}
              </template>
            </el-table-column>
          </el-table>
        </template>
      </el-table-column> -->

      <el-table-column fixed="left" label="系统名称" min-width="130">
        <!-- ★ 新增：表头筛选（母设备 名称/编码） -->
        <template #header>
          <ThRemoteSelect
            v-model="filters.muName.display"
            label="系统名称"
            :fetch-options="fetchMuName"
            :min-chars="2"
            placeholder="下拉搜索"
            @change="
              (val) => {
                const { text, id } = normalizeSelectChange(val);
                filters.muName.text = text;
                filters.muName.id = id;
                scheduleSearch();
              }
            "
            @clear="
              () => {
                filters.muName = { display: null, text: '', id: null };
                scheduleSearch();
              }
            "
            @visible-change="
              (v) => {
                if (v) {
                  /* 首开已内置预取，无需额外处理 */
                }
              }
            "
          />
        </template>

        <template #default="{ row }">
          <div class="cell-flex">
            <div class="cell-left">
              <!-- <el-icon><Setting /></el-icon> -->
              <el-tag size="small" type="primary">系统</el-tag>
              <span class="name">{{ row.name }}</span>
            </div>
            <!-- <div class="cell-right">
              <el-tag size="small" type="info">{{ row.code }}</el-tag>
              <el-tag size="small" type="primary">母设备</el-tag>
            </div> -->
          </div>
        </template>
      </el-table-column>

      <el-table-column label="系统编码" width="200" align="left">
        <template #header>
          <ThRemoteSelect
            v-model="filters.MuCode.display"
            label="系统编码"
            :fetch-options="fetchMuCode"
            :min-chars="1"
            @change="
              (val) => {
                const { text, id } = normalizeSelectChange(val);
                filters.MuCode.text = text;
                filters.MuCode.id = id;
                scheduleSearch();
              }
            "
            @clear="
              () => {
                filters.MuCode = { display: null, text: '', id: null };
                scheduleSearch();
              }
            "
          />
        </template>
        <template #default="{ row }">
          {{ row.code }}
        </template>
      </el-table-column>
     

      <el-table-column label="层级路径" min-width="360" show-overflow-tooltip>
        <template #default="{ row }">{{ fullPathOf(row) }}</template>
      </el-table-column>

      <el-table-column label="设备规格" width="160">
        <template #default="{ row }">
          {{ row.specification || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备型号" width="160">
        <template #default="{ row }">
          {{ row.model || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备材质" width="160">
        <template #default="{ row }">
          {{ row.material || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备品牌" width="160">
        <template #default="{ row }">
          {{ row.brand || "—" }}
        </template>
      </el-table-column>

      <el-table-column label="设备类别" width="160">
        <template #default="{ row }">
          {{ row.beType || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备区域" width="160">
        <template #default="{ row }">
          {{ row.area || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备模块" width="160">
        <template #default="{ row }">
          {{ row.mokuai || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设计温度(℃)" width="160">
        <template #default="{ row }">
          {{ row.designTemp || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设计压力(MPa)" width="160">
        <template #default="{ row }">
          {{ row.designPress || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="电机功率(KW)" width="160">
        <template #default="{ row }">
          {{ row.motorPower || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="搅拌形式" width="160">
        <template #default="{ row }">
          {{ row.mixingType || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备单位" width="160">
        <template #default="{ row }">
          {{ row.unit || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备数量" width="160">
        <template #default="{ row }">
          {{ row.quantity || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备金额(万)" width="160">
        <template #default="{ row }">
          {{ row.amount || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="设备品牌" width="160">
        <template #default="{ row }">
          {{ row.brand || "—" }}
        </template>
      </el-table-column>
      <el-table-column label="制造厂家" width="160">
        <template #default="{ row }">
          {{ row.manufacturer || "—" }}
        </template>
      </el-table-column>

      <!-- 分组表头：备件信息 -->
      <!-- ✅ 放在 <el-table> 的直接子元素里 -->
      <el-table-column label="备件信息" header-align="center" align="center">
        <!-- 备件名称 -->
        <el-table-column label="备件名称" min-width="160" align="left">
          <template #default="scope">
            <div
              v-if="
                Array.isArray(scope.row.spareInfos) &&
                scope.row.spareInfos.length
              "
              class="stack"
            >
              <div
                v-for="(sp, i) in scope.row.spareInfos"
                :key="sp.id ?? i"
                class="line one-line"
              >
                <el-tooltip
                  :content="sp.code ? `${sp.name}（${sp.code}）` : sp.name"
                  placement="top"
                >
                  <span>{{ sp.name || "-" }}</span>
                </el-tooltip>
              </div>
            </div>
            <span v-else>-</span>
          </template>
        </el-table-column>

        <!-- 总库存 -->
        <el-table-column label="总库存" width="90" align="center">
          <template #default="scope">
            <div
              v-if="
                Array.isArray(scope.row.spareInfos) &&
                scope.row.spareInfos.length
              "
              class="stack num-col"
            >
              <div
                v-for="(sp, i) in scope.row.spareInfos"
                :key="sp.id ?? i"
                class="line"
              >
                {{ sp.stockTotal ?? "-" }}
              </div>
            </div>
            <span v-else>-</span>
          </template>
        </el-table-column>

        <!-- 可用库存 -->
        <el-table-column label="当前库存" width="100" align="center">
          <template #default="scope">
            <div
              v-if="
                Array.isArray(scope.row.spareInfos) &&
                scope.row.spareInfos.length
              "
              class="stack num-col"
            >
              <div
                v-for="(sp, i) in scope.row.spareInfos"
                :key="sp.id ?? i"
                class="line"
              >
                {{ sp.stockAvailable ?? "-" }}
              </div>
            </div>
            <span v-else>-</span>
          </template>
        </el-table-column>

        <el-table-column label="已用数量" width="100" align="center">
          <template #default="scope">
            <div
              v-if="
                Array.isArray(scope.row.spareInfos) &&
                scope.row.spareInfos.length
              "
              class="stack num-col"
            >
              <div
                v-for="(sp, i) in scope.row.spareInfos"
                :key="sp.id ?? i"
                class="line"
              >
                {{ sp.stockTotal - sp.stockAvailable ?? "-" }}
              </div>
            </div>
            <span v-else>-</span>
          </template>
        </el-table-column>
      </el-table-column>

      <!-- <el-table-column label="备件名称" min-width="160">
  <template #default="{ row }">
    <div v-if="row.spareInfos?.length" class="stack">
      <div v-for="(sp, i) in row.spareInfos" :key="sp.id || i" class="line one-line">
        <el-tooltip :content="sp.code ? `${sp.name}（${sp.code}）` : sp.name" placement="top">
          <span>{{ sp.name || '-' }}</span>
        </el-tooltip>
      </div>
    </div>
    <span v-else>-</span>
  </template>
</el-table-column>

 
<el-table-column label="总库存" width="90" align="center">
  <template #default="{ row }">
    <div v-if="row.spareInfos?.length" class="stack num-col">
      <div v-for="(sp, i) in row.spareInfos" :key="sp.id || i" class="line">
        {{ sp.stockTotal ?? '-' }}
      </div>
    </div>
    <span v-else>-</span>
  </template>
</el-table-column>

 
<el-table-column label="可用库存" width="100" align="center">
  <template #default="{ row }">
    <div v-if="row.spareInfos?.length" class="stack num-col">
      <div v-for="(sp, i) in row.spareInfos" :key="sp.id || i" class="line">
        {{ sp.stockAvailable ?? '-' }}
      </div>
    </div>
    <span v-else>-</span>
  </template>
</el-table-column> -->

      <el-table-column
        prop="createTime"
        label="创建时间"
        width="120"
        sortable="custom"
      >
        <template #default="{ row }">
          <el-tooltip
            v-if="row.createTime"
            effect="dark"
            :content="parseTime(row.createTime, '{y}-{m}-{d} {h}:{i}:{s}')"
            placement="top"
          >
            <span>{{ parseTime(row.createTime, "{y}-{m}-{d}") }}</span>
          </el-tooltip>
          <span v-else>-</span>
        </template>
      </el-table-column>

      <el-table-column
        label="更新时间"
        prop="updateTime"
        width="120"
        sortable="custom"
      >
        <template #default="{ row }">
          <el-tooltip
            v-if="row.updateTime"
            effect="dark"
            :content="parseTime(row.updateTime, '{y}-{m}-{d} {h}:{i}:{s}')"
            placement="top"
          >
            <span>{{ parseTime(row.updateTime, "{y}-{m}-{d}") }}</span>
          </el-tooltip>
          <span v-else>-</span>
        </template>
      </el-table-column>

      <el-table-column label="操作" width="110" fixed="right" align="center">
        <template #default="{ row }">
          <div class="op-grid">
            <div class="op-cell">
              <el-tooltip content="详情">
                <el-button
                  class="op-btn"
                  link
                  size="small"
                  :icon="Search"
                  @click.stop="openDetail(row)"
                />
              </el-tooltip>
            </div>
            <div class="op-cell">
              <el-tooltip content="修改">
                <el-button
                  class="op-btn"
                  link
                  size="small"
                  :icon="Edit"
                  @click.stop="openEdit(row)"
                />
              </el-tooltip>
            </div>
            <div class="op-cell">
              <el-tooltip content="新增子项">
                <el-button
                  class="op-btn"
                  link
                  size="small"
                  :icon="Plus"
                  @click.stop="openAddChild(row)"
                />
              </el-tooltip>
            </div>
            <div class="op-cell">
              <el-tooltip content="历史" placement="top">
                <el-button
                  class="op-btn"
                  link
                  size="small"
                  :icon="Timer"
                  @click.stop="getHistory(row)"
                />
              </el-tooltip>
            </div>
            <div class="op-cell">
              <el-tooltip content="复制路径">
                <el-button
                  class="op-btn"
                  link
                  size="small"
                  :icon="CopyDocument"
                  @click.stop="copyPath(row)"
                />
              </el-tooltip>
            </div>
            <div class="op-cell">
              <el-tooltip content="删除">
                <el-button
                  class="op-btn"
                  link
                  size="small"
                  type="danger"
                  :icon="Close"
                  @click.stop="removeNode(row)"
                />
              </el-tooltip>
            </div>
          </div>
        </template>
      </el-table-column>
    </el-table>

    <pagination
      v-show="total > 0"
      :total="total"
      v-model:page="q.pageNum"
      v-model:limit="q.pageSize"
      @pagination="runSearch"
    />

    <!-- <div v-show="total > 0" class="pagination-affix-wrap"> 
  <el-affix :target="affixTarget" position="bottom" :offset="0">
    <div class="pagination-affix">
      <pagination
        :total="total"
        v-model:page="q.pageNum"
        v-model:limit="q.pageSize"
        @pagination="runSearch"
      />
    </div>
  </el-affix>
</div> -->

    <!-- 子设备弹窗（卡片式，每行 2 个） -->

    <!-- 详情弹窗 -->
    <el-dialog v-model="detailVisible" title="节点详情" width="880px">
      <el-descriptions :column="2" border>
        <el-descriptions-item label="设备路径" :span="2">
          <el-tooltip :content="fullPathOf(detailRow)" placement="top">
            <span class="one-line">{{ fullPathOf(detailRow) }}</span>
          </el-tooltip>
        </el-descriptions-item>
        <el-descriptions-item label="设备名称">{{
          detailRow?.name || "-"
        }}</el-descriptions-item>
        <el-descriptions-item label="设备编码">{{
          detailRow?.code || "-"
        }}</el-descriptions-item>
        <el-descriptions-item label="设备类型">{{
          detailRow?.type || "-"
        }}</el-descriptions-item>
        <!-- <el-descriptions-item label="设备层级">{{ detailRow?.level ?? '-' }}</el-descriptions-item> -->

        

        <el-descriptions-item label="创建时间">
          {{
            detailRow?.createTime
              ? parseTime(detailRow.createTime, "{y}-{m}-{d} {h}:{i}:{s}")
              : "-"
          }}
        </el-descriptions-item>
        <el-descriptions-item label="更新时间">
          {{
            detailRow?.updateTime
              ? parseTime(detailRow.updateTime, "{y}-{m}-{d} {h}:{i}:{s}")
              : "-"
          }}
        </el-descriptions-item>
        <el-descriptions-item label="备注说明" :span="2">
          <span class="one-line">{{ detailRow?.remark || "-" }}</span>
        </el-descriptions-item>

        <el-descriptions-item label="备件信息" :span="2">
  <div
    class="spare-grid"
    v-if="Array.isArray(detailRow?.spareInfos) && detailRow.spareInfos.length"
  >
    <!-- 表头 -->
    <div class="head">名称</div>
    <div class="head">编码</div>
    <div class="head">当前库存</div>
    <div class="head">总库存</div>
    <div class="head">使用数量</div>

    <!-- 行 -->
    <template v-for="(s,i) in detailRow.spareInfos" :key="s.id ?? i">
      <div class="cell name" :title="s?.name">
        <span class="truncate">{{ s?.name ?? '—' }}</span>
      </div>
      <div class="cell code">
        <el-tag v-if="s?.code" size="small" class="code-pill">{{ s.code }}</el-tag>
        <span v-else>—</span>
      </div>
      <div class="cell">
        <span class="badge info">{{ s?.stockAvailable ?? 0 }}</span>
      </div>
      <div class="cell">
        <span class="badge">{{ s?.stockTotal ?? 0 }}</span>
      </div>
      <div class="cell">
        <span class="badge warn">
          {{ Math.max(0, Number(s?.stockTotal ?? 0) - Number(s?.stockAvailable ?? 0)) }}
        </span>
      </div>
    </template>
  </div>
  <span v-else>—</span>
</el-descriptions-item>



      </el-descriptions>
      <template #footer
        ><el-button @click="detailVisible = false">关闭</el-button></template
      >
    </el-dialog>

    <el-dialog
      v-model="historyVisible"
      :title="`设备备件更换历史 - ${historyAsset?.name || ''}`"
      width="920px"
      :close-on-click-modal="false"
    >
      <!-- 筛选条 -->
      <div class="mb12" style="display: flex; gap: 10px; align-items: center">
        <el-input
          v-model="historyQuery.keyword"
          placeholder="备件名称/编码"
          clearable
          style="width: 240px"
          @keyup.enter="onDateChange()"
          @clear="onDateChange()"
        />

        <DateRangePicker
          v-model="historyQuery.dateRangeDay"
          :shortcuts="[
            {
              text: '本月',
              value: () => {
                const now = new Date();
                const s = new Date(now.getFullYear(), now.getMonth(), 1);
                const e = new Date(now.getFullYear(), now.getMonth() + 1, 0);
                return [s, e];
              },
            },
          ]"
          @normalized="onNorm"
          @change="onDateChange"
        />

        <!-- <DateTimeRangePicker v-model="historyQuery.dateRangeSecond" @change="onDateTimeChange" /> -->

        <!-- <el-button type="primary" @click="fetchHistory">查询</el-button> -->
        <el-button type="primary" @click="resetQuery">重置</el-button>
        <!-- <div style="flex: 1"></div>
        <el-tag type="info" effect="text"
          >设备：{{ historyAsset?.name }}（{{
            historyAsset?.code || "无编码"
          }}）</el-tag > -->
      </div>

      <el-table
        :data="historyRows"
        v-loading="historyLoading"
        border
        stripe
        size="small"
        style="height: 400px"
        @sort-change="handleSortChange"
      >
        <!-- <el-table-column prop="createTime" label="更换时间" width="160" align="center"> 
          <template #default="{ row }">
            {{ parseTime(row.createTime, '{y}-{m}-{d}') }}
          </template>
        </el-table-column> -->

        <el-table-column
          prop="createTime"
          label="创建时间"
          width="180"
          sortable="custom"
        >
          <template #default="{ row }">
            <el-tooltip
              v-if="row.createTime"
              effect="dark"
              :content="parseTime(row.createTime, '{y}-{m}-{d} {h}:{i}:{s}')"
              placement="top"
            >
              <span>{{ parseTime(row.createTime, "{y}-{m}-{d}") }}</span>
            </el-tooltip>
            <span v-else>-</span>
          </template>
        </el-table-column>

        <el-table-column
          prop="spareName"
          label="备件名称"
          min-width="120"
          show-overflow-tooltip
        />

        <el-table-column
          prop="qty"
          label="更换数量"
          width="80"
          align="center"
        />
        <el-table-column
          prop="spareStockTotal"
          label="总库存"
          width="90"
          align="center"
        />
        <el-table-column
          prop="spareStockAvailable"
          label="当前库存"
          width="100"
          align="center"
        />
        <el-table-column prop="" label="使用数量" width="160" align="center">
          <template #default="{ row }">
            {{ row.spareStockTotal - row.spareStockAvailable }}
          </template>
        </el-table-column>
        <!-- <el-table-column
          prop="remark"
          label="备注"
          min-width="160"
          show-overflow-tooltip
        /> -->
        <el-table-column
          prop="createBy"
          label="更换人"
          width="100"
          align="center"
        />
      </el-table>

      <!-- <pagination
          v-show="historyTotal > 0"
          :total="historyTotal"
          v-model:page="historyQuery.pageNum"
          v-model:limit="historyQuery.pageSize"
          @pagination="fetchHistory"
        /> -->
      <template #footer>
        <el-button @click="historyVisible = false">关闭</el-button>
      </template>
    </el-dialog>

    <el-dialog
      v-model="editVisible"
      v-loading="editLoading"
      :title="editForm.id ? '修改节点' : '新增子节点'"
      width="1300px"
      :close-on-click-modal="false"
    >
    <el-form
        ref="editRef"
        :model="editForm"
        :rules="editRules"
        label-width="96px"
      >
        <el-row :gutter="16">
          <el-col :span="12">
            <el-form-item label="新父节点">
              <el-cascader
              :key="cascaderKey"         
              v-if="showCascader"              

              v-model="rootPath"
              :props="cProps"                      
              :clearable="cascaderClearable"        
              :placeholder="cascaderPlaceholder"
              filterable
              show-all-levels
              style="min-width: 360px" 
              >
            <!-- 每项右侧显示类型徽标（根据后端返回的 nodeType 渲染） -->
            <template #default="{ node, data }">
              <div class="cas-opt">
                <span class="cas-label" :title="data.label">{{
                  data.label
                }}</span>
                <el-tag
                  v-if="typeLabelOf(node, data)"
                  size="small"
                  :type="typeTagType(typeLabelOf(node, data))"
                  effect="light"
                  class="cas-badge"
                >
                  {{ typeLabelOf(node, data) }}
                </el-tag>
              </div>
            </template>

            <!-- 选择后，输入框里每段路径的展示（可选） -->
            <template #tag="{ node, data }">
              <span class="sel-tag">
                {{ data.label }}
                <span class="sel-type" v-if="typeLabelOf(node, data)"
                  >（{{ typeLabelOf(node, data) }}）</span
                >
              </span>
            </template>
          </el-cascader>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="当前父节点">
              <el-input
                :model-value="parentPathText(editForm.parentId)"
                disabled
              />
            </el-form-item>
          </el-col>

          <el-col :span="6">
            <el-form-item label="设备类型" prop="nodeType"> 
              <el-select-v2
                v-model="editForm.nodeType"
                :options="typeOptions"
                placeholder="请选择设备类型"
                clearable
                filterable
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备编码" prop="code">
              <el-input v-model="editForm.code" placeholder="必填" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备名称" prop="name">
              <el-input v-model="editForm.name" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="归属部门" prop="deptId">
              <el-tree-select
                v-model="editForm.deptId"
                :data="deptOptions"
                :props="{ value: 'id', label: 'label', children: 'children' }"
                value-key="id"
                placeholder="请选择归属部门"
                check-strictly
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备类别">
              <el-input v-model="editForm.beType" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备区域">
              <el-input v-model="editForm.area" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备模块">
              <el-input v-model="editForm.mokuai" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设计温度">
              <el-input v-model="editForm.designTemp" placeholder="(单位:℃ )" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设计压力">
              <el-input v-model="editForm.designPress" placeholder="(单位:MPa )" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="电机功率">
              <el-input v-model="editForm.motorPower" placeholder="填数值即可 (单位:KW )" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="搅拌形式">
              <el-input v-model="editForm.mixingType" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备单位">
              <el-input v-model="editForm.unit" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备数量">
              <el-input v-model="editForm.quantity" placeholder="" />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备金额">
              <el-input v-model="editForm.amount" placeholder="填数值即可 (单位:万 )" />
            </el-form-item>
          </el-col>

          
          <el-col :span="6">
            <el-form-item label="备件名称">
              <el-select-v2
                v-model="editForm.spareIds"
                :options="spareListOptions"
                placeholder="可多选"
                multiple
                collapse-tags
                collapse-tags-tooltip
                filterable
                clearable
                style="width: 100%"
              />
            </el-form-item>
          </el-col>
          

          <el-col :span="6">
            <el-form-item label="特种设备">
              <el-select
                v-model.number="editForm.isSpecial"
                placeholder="请选择类型"
                clearable
              >
                <el-option label="是" :value="1" />
                <el-option label="否" :value="0" />
              </el-select>
            </el-form-item>
          </el-col>

          <el-col :span="6">
            <el-form-item label="设备规格" prop="specification">
              <el-input
                v-model="editForm.specification"
                maxlength="100"
                show-word-limit
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备型号" prop="model">
              <el-input
                v-model="editForm.model"
                maxlength="100"
                show-word-limit
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="设备材质" prop="material">
              <el-input
                v-model="editForm.material"
                maxlength="60"
                show-word-limit
              />
            </el-form-item>
          </el-col> 
          <el-col :span="6">
            <el-form-item label="设备品牌" prop="brand">
              <el-input
                v-model="editForm.brand"
                maxlength="60"
                show-word-limit
              />
            </el-form-item>
          </el-col>
          <el-col :span="6">
            <el-form-item label="制造厂家" prop="manufacturer">
              <el-input
                v-model="editForm.manufacturer"
                maxlength="120"
                show-word-limit
              />
            </el-form-item>
          </el-col>

          <el-col :span="6">
            <el-form-item label="设备状态" prop="status">
              <el-select
                v-model.number="editForm.status"
                placeholder="请选择设备状态"
              >
                <el-option
                  v-for="dict in asset_node_status"
                  :key="dict.value"
                  :label="dict.label"
                  :value="Number(dict.value)"
                ></el-option>
              </el-select>
            </el-form-item>
          </el-col>

          <el-col :span="24">
            <el-form-item label="备注">
              <el-input v-model="editForm.remark" type="textarea" rows="2" />
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>

      <template #footer>
        <el-button @click="editVisible = false">取消</el-button>
        <el-button type="primary" @click="submitEdit">保存</el-button>
      </template>
    </el-dialog>

    <el-dialog
      v-model="childrenVisible"
      :title="motherCurrent?.name || '设备详情'"
      width="1200px"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
      :destroy-on-close="true"
      @open="activeTab = 'base'"
      @closed="activeTab = 'base'"
    >
      <FeatureTabsContainer
        v-model="activeTab"
        :panes="panesForDialog"
        :keepAlive="false"
        style="--ft-primary: #3e8cf0"
        :content-height="420"
        v-loading="dialogLoading"
        element-loading-text="正在加载组成与明细…"
        element-loading-background="rgba(255,255,255,.6)"
      />
    </el-dialog>

    <AddPlanDrawer
      v-model="addPlanVisible"
      :asset="addPlanAsset"
      @success="onPlanCreated"
    />
    <!-- style="background-color:#accdfc" -->

    <el-dialog
      v-model="childrenVisible22"
      :title="motherCurrent?.name || '设备详情'"
      width="1200px"
      :close-on-click-modal="false"
      :close-on-press-escape="false"
    >
      <AssetTreeTable
        ref="treeTableRef"
        :startParentId="editParentId"
        width="100%"
        height="300px"
        :leftSpan="6"
        :tableHeight="560"
        :showSearch="false"
        :pageSizeDefault="20"
        @node-click="onNodeClick"
        @row-dblclick="onRowDblclick"
        @loaded="onLoaded"
      >
      </AssetTreeTable>
    </el-dialog>
  </div>
</template>

<script setup>
import {
  ref,
  reactive,
  computed,
  onMounted,
  nextTick,
  getCurrentInstance,
  watch,
} from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import {
  Search,
  Refresh,
  Download,
  Close,
  Plus,
  CopyDocument,
  Setting,
  Edit,
  Tools,
  Cpu,
  Timer,
} from "@element-plus/icons-vue";
import {
  listSbtz,
  listParentId,
  getSbtz,
  delSbtz,
  cascaderChildren,
  createRoute,
  rehomeRoute,
  listSearchData,
} from "@/api/sbtz/sbtz";
import { listSbtype } from "@/api/sbtz/sbtype";
import { loadAllParams } from "@/api/page";
import { listMtplanSpareHistory } from "@/api/sbtz/mtplan";
import { listOrderSpareHistory } from "@/api/sbtz/order";
import { listSpare } from "@/api/sbtz/spare";
import AssetPathWizardOnly from "@/components/sbtz/AssetPathWizardOnly.vue";
import TopStripTabs from "@/components/sbtz/dao/TopStripTabs.vue";
import CardTabs from "@/components/sbtz/dao/CardTabs.vue";
// 你的模块（建议各自封装为独立 SFC）
import BaseInfo from "@/components/sbtz/dao/BaseInfo.vue";
import Composition from "@/components/sbtz/dao/Composition.vue";
import Usage from "@/components/sbtz/dao/Usage.vue";
import UsageData from "@/components/sbtz/dao/UsageData.vue";
import Maintenance from "@/components/sbtz/dao/Maintenance.vue";
import AssetTreeTable from "@/components/sbtz/AssetTreeTable.vue";
import ThRemoteSelect from "@/components/sbtz/remote/ThRemoteSelect.vue";

import FeatureSwitch from "@/components/sbtz/single/FeatureSwitch.vue";
import ConfigPage from "@/components/sbtz/single/ConfigPage.vue";

import FeatureTabsContainer from "@/components/sbtz/single/FeatureTabsContainer.vue";
import CompositionTreeRoot from "@/components/sbtz/single/panes/CompositionTreeRoot.vue";
const BaseFormPane = defineAsyncComponent(() =>
  import("@/components/sbtz/single/panes/BaseFormPane.vue")
);
const MailFormPane = defineAsyncComponent(() =>
  import("@/components/sbtz/single/panes/MailFormPane.vue")
);
import KeyValueEditor from "@/components/sbtz/single/panes/KeyValueEditor.vue";
import EquipmentDataDemo from "@/components/sbtz/single/panes/EquipmentDataDemo.vue";
import EquipmentUsageDemo from "@/components/sbtz/single/panes/EquipmentUsageDemo.vue";
import WorkOrder from "@/components/sbtz/single/panes/WorkOrder.vue";
import PlanWeihuAssetPane from "@/components/sbtz/single/panes/PlanWeihuAssetPane.vue";
import AddPlanDrawer from "@/components/sbtz/plan/AddPlanDrawer.vue";

import ChildCardsGrid from "@/components/sbtz/single/panes/ChildCardsGrid.vue";
import { deptTreeSelect } from "@/api/system/user";
import { status } from "nprogress";
import ExcelUploader from "@/components/sbtz/file/ExcelUploader.vue";
import SystemCreateDialog from "@/components/sbtz/createSystem/SystemCreateDialog.vue";


function onImported() {
  // 例如成功后刷新列表
  // await runSearch()
}

const editLoading = ref(false);
const rootPath = ref(null)
const cascaderKey = ref(0)
const showCascader = ref(true)
const show = ref(false);
// —— 把 descendants + motherCurrent 拼树（父页会自动响应更新）——
const deviceTree = computed(() =>
  buildDeviceTree(motherCurrent.value, descendants.value)
);
const detailMapObj = computed(() => {
  const obj = {};
  idMap.value.forEach((row, id) => (obj[id] = row));
  return obj;
});

const activeTab = ref("base");
const deptOptions = ref(undefined);

const addPlanVisible = ref(false);
const addPlanAsset = ref(null);

// 只要 ++ 就能通知子面板重新拉列表
const planReloadTick = ref(0);
const dblBusy = ref(false); // 双击总开关，给表格加遮罩
const dialogLoading = ref(false); // 弹窗内部 loading

function openAddPlan(row) {
  addPlanAsset.value = row;
  addPlanVisible.value = true;
}

function onPlanCreated() {
  planReloadTick.value++; // 新增成功 -> 通知子面板刷新
} 
 
// 禁用部门目录
const deptMap = new Map();  
function getDeptTree() {
  deptTreeSelect().then(response => {
    deptMap.clear();
    deptOptions.value = buildDeptTree(response.data || []);
  });
}
function buildDeptTree(nodes = []) {
  return nodes.map(n => {
    const children = buildDeptTree(n.children || []);
    const node = {
      ...n,
      children,
      // 规则：有 children 的视为目录/公司层，禁用选择；叶子可选
      disabled: children.length > 0
    };
    deptMap.set(n.id, node);
    return node;
  });
}
// 禁用部门目录 end

function onDateChange() {
  // val 是归整过的 ['2025-09-01 00:00:00','2025-09-24 23:59:59']
  historyQuery.pageNum = 1;
  fetchHistory();
}

 
const fetchMuName = async (q) => {
  const { rows = [] } = await listSbtz({
    pageNum: 1,
    pageSize: 1000,
    nodeType: "SYSTEM",
    name: q || null,
  });
  return rows.map((r) => ({
    value: r.id,
    label: r.name,
    id: r.id,
    text: r.name,
  }));
};

 
const fetchMuCode = async (q) => {
  const { rows = [] } = await listSbtz({
    pageNum: 1,
    pageSize: 1000,
    nodeType: "SYSTEM",
    code: q || null,
  });
  return rows.map((r) => ({
    value: r.id,
    label: r.code,
    id: r.id,
    text: r.code,
  }));
};

// 3) 系统名称（根层 parentId=0 by name）
const fetchSystemName = async (q) => {
  const { rows = [] } = await listSbtz({
    pageNum: 1,
    pageSize: 1000,
    parentId: 0, // 根层系统
    name: q || null,
  });
  return rows.map((r) => ({
    value: r.id,
    label: r.name, // 下拉显示
    id: r.id,
    text: r.name,
  }));
};

// 4) 系统编码（根层 parentId=0 by code）
const fetchSystemCode = async (q) => {
  const { rows = [] } = await listSbtz({
    pageNum: 1,
    pageSize: 1000,
    parentId: 0,
    code: q || null,
  });
  return rows.map((r) => ({
    value: r.id,
    label: r.code,
    id: r.id,
    text: r.code,
  }));
};

// 小工具：把 change 的返回值统一成 { text, id }
const normalizeSelectChange = (val) => {
  if (typeof val === "string") return { text: val.trim(), id: null };
  return {
    text: val?.text ?? val?.label ?? "",
    id: val?.id ?? val?.value ?? null,
  };
};

const panesForDialog = [
  {
    key: "base",
    label: "设备基本信息",
    component: BaseFormPane,
    // load: () => api.getBaseCfg(),         // 首次进入时加载一次
    // BaseFormPane 使用 v-model="modelValue" + @save，自然对接
    props: () => ({
      row: motherCurrent.value,
    }),
  },
  {
    key: "group",
    label: "设备组成信息",
    component: CompositionTreeRoot,
    // 直接把树(ref)作为数据源传入容器
    data: deviceTree,
    props: (tree) => ({
      nodes: tree,
      detailMap: detailMapObj.value,
      getPath: fullPathOf,
      loadDetail: (id) =>
        getSbtz(id).then((r) => {
          console.log('[getSbtz]', id, r);      // 打印整个响应
          console.log('[getSbtz.data]', r?.data); // 只看 data
          return adaptRow(r.data);               // 继续原有逻辑
        }),


      // 复用父级已有逻辑
      onAdd: (row) => openAddChild(row),
      onEdit: (row) => openEdit(row),
      onRemove: (row) => removeNode(row),
      onHistory: (row) => getHistory(row),
      onRefresh: () => loadAllDescendants(motherCurrent.value?.id),
    }),
  },
  {
    key: "quick",
    label: "设备使用信息",
    component: EquipmentUsageDemo,
    props: () => ({
      row: motherCurrent.value,
      // items: usageItems.value,
    }),
  },
  {
    key: "append",
    label: "设备使用数据",
    component: EquipmentDataDemo,
    props: () => ({
      row: motherCurrent.value,
      items: usageItems.value,
    }),
  },
  {
    // key:'weihu',
    // label:'设备维护维修数据',
    // component: KeyValueEditor,
    // data: [ { key:'示例A', value:'/a' } ],
    key: "weihu",
    label: "设备维护维修数据",
    component: WorkOrder,
    props: () => ({
      row: motherCurrent.value,
    }),
  },
  {
    key: "plan",
    label: "计划维护维修",
    component: PlanWeihuAssetPane,
    props: () => ({
      row: motherCurrent.value,
      onAddPlan: openAddPlan,
      refreshTick: planReloadTick.value,
    }),
  },

  // 你还可以把“基本信息/使用信息”等也配进来
];

const historyVisible = ref(false);
const historyLoading = ref(false);
const historyRows = ref([]);
const historyTotal = ref(0);
const historyQuery = reactive({
  pageNum: 1,
  pageSize: 10,
  keyword: "",
  dateRange: null,
  orderId: null,
  orderByColumn: "createTime",
  isAsc: "desc",
  dateRangeDay: null,
  dateRangeSecond: null,
});
const historyAsset = ref(null);

async function getHistory(row) {
  historyAsset.value = row;
  historyQuery.pageNum = 1;
  await fetchHistory();
  historyVisible.value = true;
}

// 设置日期范围为当前月
function setThisMonth() {
  const d = new Date();
  const y = d.getFullYear();
  const m = d.getMonth() + 1;
  const pad = (n) => String(n).padStart(2, "0"); // 补零函数
  historyQuery.dateRange = [
    `${y}-${pad(m)}-01 00:00:00`, // 本月开始
    `${y}-${pad(m)}-${pad(new Date(y, m, 0).getDate())} 23:59:59`, // 本月结束
  ];
}

// 获取历史数据
// 可选：把空值清掉，避免传一堆 null/undefined/空串
function cleanParams(obj) {
  const out = {};
  Object.keys(obj).forEach((k) => {
    const v = obj[k];
    if (v !== null && v !== undefined && v !== "") out[k] = v;
  });
  return out;
}

async function fetchHistory() {
  const id = historyAsset?.value?.id;
  if (!id) {
    historyRows.value = [];
    historyTotal.value = 0;
    return;
  }

  // const range = Array.isArray(historyQuery.dateRangeDay) ? historyQuery.dateRangeDay : []
  // const startTime = range.length > 0 ? range[0] : null
  // const endTime   = range.length > 1 ? range[1] : null

  const sec = Array.isArray(historyQuery.dateRangeSecond)
    ? historyQuery.dateRangeSecond
    : null;
  const day = Array.isArray(historyQuery.dateRangeDay)
    ? historyQuery.dateRangeDay
    : null;
  const [startTime, endTime] =
    sec && sec.length === 2
      ? sec
      : day && day.length === 2
      ? [day[0] + " 00:00:00", day[1] + " 23:59:59"]
      : [null, null];

  const rawParams = {
    assetId: id,
    pageNum: historyQuery.pageNum ?? 1,
    pageSize: historyQuery.pageSize ?? 20,
    keyword: historyQuery.keyword ?? null,
    orderByColumn: historyQuery.orderByColumn,
    isAsc: historyQuery.isAsc,
    "params[beginTime]": startTime,
    "params[endTime]": endTime,
  };

  const params = cleanParams(rawParams);

  const { rows, total } = await listOrderSpareHistory(params);
  historyRows.value = rows || [];
  historyTotal.value = Number(total || 0);
}

function onDateTimeChange(val) {
  historyQuery.pageNum = 1;
  fetchHistory();
}

function resetQuery() {
  historyQuery.keyword = "";
  historyQuery.dateRangeSecond = null; // ✅ 清空秒级
  historyQuery.dateRangeDay = null; // ✅ 清空天级
  historyQuery.pageNum = 1;
  fetchHistory();
}

function handleSortChange({ prop, order }) {
  // prop 就是列字段，这里是 createTime
  // order 是 'ascending' | 'descending' | null
  historyQuery.orderByColumn = prop;
  historyQuery.isAsc = order === "ascending" ? "asc" : "desc";
  historyQuery.pageNum = 1;
  fetchHistory();
}

function mainSortChange({ prop, order }) {
  // order: 'ascending' | 'descending' | null
  q.orderByColumn = prop || null;
  q.isAsc =
    order === "ascending" ? "asc" : order === "descending" ? "desc" : null;
  q.pageNum = 1;
  runSearch(); // 或 fetchMothers()
}

// 重置查询
function resetHistoryFilter() {
  historyQuery.keyword = "";
  historyQuery.dateRange = null; // 重置日期范围
  historyQuery.pageNum = 1;
  fetchHistory();
}

// const usageItems = computed(() => {
//   const mk = (r = {}) => ({
//     id: r.id,
//     assetName: r.name,
//     status: r.runStatus || 'active',
//     enabledAt: r.enabledAt || r.createTime || null,
//     department: r.department || r.dept || '-',
//     useRange: (r.useStart && r.useEnd) ? { start: r.useStart, end: r.useEnd } : null,
//     idleRange: (r.idleStart && r.idleEnd) ? { start: r.idleStart, end: r.idleEnd } : null,
//     stopAt: r.stopAt || r.disableTime || null,
//   })
//   const list = []
//   if (motherCurrent.value) list.push(mk(motherCurrent.value))
//   for (const r of descendants.value) list.push(mk(r))
//   return list
// })

const usageItems = computed(() => {
  const mk = (r = {}) => {
    // 如需调试，保留此行；不需要就删掉

    // 区间对象（确保 start <= end）
    const mkRange = (a, b) => {
      if (!a || !b) return null;
      const start = a <= b ? a : b;
      const end = a <= b ? b : a;
      return { start, end };
    };

    return {
      id:
        r.id ??
        r.assetId ??
        r.code ??
        crypto?.randomUUID?.() ??
        String(Math.random()),
      assetName: r.name ?? r.assetName ?? "-",
      status: r.runStatus ?? "active", // 有真实字段就替换
      enabledAt: r.enabledAt ?? r.createTime ?? null,
      department: r.department ?? r.dept ?? "-",
      useRange: mkRange(r.useStart, r.useEnd),
      idleRange: mkRange(r.idleStart, r.idleEnd),
      stopAt: r.stopAt ?? r.disableTime ?? null,
    };
  };

  const list = [];
  if (motherCurrent.value) list.push(mk(motherCurrent.value));
  (descendants.value || []).forEach((r) => list.push(mk(r)));

  // 去重（按 id，保序）
  const seen = new Set();
  return list.filter((it) => {
    if (!it) return false;
    if (seen.has(it.id)) return false;
    seen.add(it.id);
    return true;
  });
});

// 顶部导航项（key 要和 modules 对齐）
const navItems = [
  { key: "base", label: "设备基本信息" },
  { key: "group", label: "设备组成信息" },
  { key: "usage", label: "设备使用信息" },
  { key: "usageData", label: "设备使用数据" },
  { key: "maintenance", label: "设备维护维修信息" },
];

const active = ref("base"); // 当前激活项
const activeIndex = computed(() =>
  navItems.findIndex((n) => n.key === active.value)
);

// 模块映射（与 key 一一对应）
const modules = {
  base: BaseInfo,
  group: Composition,
  usage: Usage,
  usageData: UsageData,
  maintenance: Maintenance,
};

// 切换
function switchNav(key, i) {
  active.value = key;
}

// 滑块样式（等分宽度）
const sliderStyle = computed(() => {
  const count = navItems.length || 1;
  return {
    width: `calc(100% / ${count})`,
    transform: `translateX(${
      (activeIndex.value < 0 ? 0 : activeIndex.value) * 100
    }%)`,
  };
});

// 顶部状态
const total = ref(0); // ★ 总条数
const showSearch = ref(true);
/** ======== 搜索与数据（仅加载母设备） ======== */
const q = reactive({
  name: "",
  code: "",
  createDate: null,
  pageNum: 1,
  pageSize: 10,
});
const motherRows = ref([]);
const idMap = ref(new Map()); // 索引：用于 pathText / parentPathText
const childrenVisible = ref(false);
const motherCurrent = ref(null);
const childFilter = ref("");

/** 详情 / 新增编辑弹窗 */
const detailVisible = ref(false);
const detailRow = ref(null);

const editVisible = ref(false);
const editRef = ref();
const editForm = reactive({
  id: null,
  parentId: null,
  nodeType: "MODULE",
  name: "",
  code: "",
  remark: "",
  specification: "",
  model: "",
  material: "",
  power: "",
  brand: "",
  manufacturer: "",
  isSpecial: null,
  status: null,
  deptId: null,
  spareIds: [],
});
const editRules = {
  name: [{ required: true, message: "设备名称必填", trigger: "blur" }],
  code: [{ required: true, message: "设备编码必填", trigger: "blur" }],
  nodeType: [{ required: true, message: "请选择类型", trigger: "change" }],
  deptId: [{ required: true, message: "请选择部门", trigger: "change" }],
  specification: [{ max: 100, message: "最多 100 字", trigger: "blur" }],
  model: [{ max: 100, message: "最多 100 字", trigger: "blur" }],
  material: [{ max: 60, message: "最多 60 字", trigger: "blur" }],
  power: [{ max: 40, message: "最多 40 字", trigger: "blur" }],
  brand: [{ max: 60, message: "最多 60 字", trigger: "blur" }],
  manufacturer: [{ max: 120, message: "最多 120 字", trigger: "blur" }],
};
const spareListOptions = ref([]);

/** 子设备（全量子孙）容器 */
const descendants = ref([]); // ★ 存放“所有子孙”的平铺列表
const filteredChildren = computed(() => {
  const kw = (childFilter.value || "").trim().toLowerCase();
  if (!kw) return descendants.value;
  return descendants.value.filter((r) =>
    ((r?.name ?? "") + (r?.code ?? "") + (r?.remark ?? ""))
      .toLowerCase()
      .includes(kw)
  );
});

function getList() {
  // right-toolbar 会触发这个
  fetchMothers();
}
function refreshByParent() {
  // 顶部 AssetPathWizardOnly 发出的刷新，按需自定义
  q.pageNum = 1;
  fetchMothers();
}

function adaptRow(r = {}) {
  const spare = r.spareInfo || {};
  const list = Array.isArray(r.spareInfos)
    ? r.spareInfos
    : r.spareInfo
    ? [r.spareInfo]
    : [];

  const childCount =
    r.childCount ??
    r.childrenCount ??
    r.subCount ??
    r.childrenNum ??
    r.childNum ??
    (Array.isArray(r.children) ? r.children.length : 0) ??
    0;

  const type = r.type || r.nodeType || "MODULE";
  return {
    id: r.id,
    pid: r.parentId ?? r.pid ?? 0,
    name: r.name || "",
    code: r.code || "",
    nodeType: r.nodeType || r.type || type,
    type,
    level: r.level ?? 1,
    remark: r.remark || "",
    createTime: r.createTime || "",
    updateTime: r.updateTime || "",
    childCount,
    specification: r.specification || "",
    model: r.model || "",
    material: r.material || "",
    power: r.power || "",
    brand: r.brand || "",
    manufacturer: r.manufacturer || "",
    isSpecial: r.isSpecial ?? null,
    status: r.status ?? null,
    deptId: r.deptId || null,

    mokuai: r.mokuai || "",
    area: r.area || "",
    designTemp: r.designTemp || "",
    designPress: r.designPress || "",
    motorPower: r.motorPower || "",
    mixingType: r.mixingType || "",
    beType: r.beType || "",
    unit: r.unit ?? null,
    quantity: r.quantity ?? null,
    amount: r.amount ?? null,

    spareInfos: list.map((s) => ({
      id: s.id ?? null,
      name: s.name ?? "",
      code: s.code ?? "",
      stockTotal: s.stockTotal ?? 0,
      stockAvailable: s.stockAvailable ?? 0,
      qtyNeed: s.qtyNeed ?? 1, // 如果你关系表返回了
      isPrimary: s.isPrimary ?? 0, // 如果你返回了
      remark: s.relRemark ?? s.remark ?? "",       
    })),
    children: r.children || [],
  };
}

// const cProps = {
//   checkStrictly: true,
//   emitPath: true, // 返回数组，但我们强制只留第一段
//   value: "id",
//   label: "label",
//   lazy: true,
//   lazyLoad: async (node, resolve) => {
//     const parentId = node.level === 0 ? 0 : node.value;
//     try {
//       const res = await cascaderChildren(parentId);
//       let list = res?.data || [];
//       if (node.level === 0) {
//         // 根层：仅保留“系统”
//         list = list
//           .filter(x => (x.nodeType || x.node_type) === "SYSTEM")
//           .map(x => ({ ...x, leaf: true })); // 系统层直接设为叶子，禁止展开
//       } else {
//         // 非根层不允许展示
//         list = [];
//       }
//       resolve(list);
//     } catch {
//       resolve([]);
//     }
//   }
// };
const TYPE_LABELS = {
  SYSTEM: "系统",
  MACHINE: "母设备",
  MODULE: "子设备",
  COMPONENT: "部件",
  PART: "零件",
};


// const cProps = {
//   checkStrictly: true,
//   emitPath: true,
//   value: 'id',
//   label: 'label', 
//   lazy: true,
//   lazyLoad: (node, resolve) => {
//     const parentId = node.level === 0 ? 0 : node.value;
//     cascaderChildren(parentId)
//       .then(res => resolve(res?.data || []))  // 需要后端返回 {id,label,leaf,nodeType}
//       .catch(() => resolve([]));
//   },
// };

 
const onlySystemRootProps = {
  checkStrictly: true,
  emitPath: true,
  value: 'id',
  label: 'label',
  lazy: true,
  async lazyLoad(node, resolve) {
    const parentId = node.level === 0 ? 0 : node.value
    try {
      const res = await cascaderChildren(parentId)
      let list = res?.data || []
      if (node.level === 0) {
        list = list
          .filter(x => (x.nodeType || x.node_type) === 'SYSTEM')
          .map(x => ({ ...x, leaf: true })) // 根层直接设为叶子，禁止继续展开
      } else {
        list = [] // 非根层不展示
      }
      resolve(list)
    } catch {
      resolve([])
    }
  }
}

/** 正常懒加载（用于其它类型） */
const normalProps = {
  checkStrictly: true,
  emitPath: true,
  value: 'id',
  label: 'label',
  lazy: true,
  async lazyLoad(node, resolve) {
    const parentId = node.level === 0 ? 0 : node.value
    try {
      const res = await cascaderChildren(parentId)
      resolve(res?.data || [])
    } catch {
      resolve([])
    }
  }
}

/** 根据类型动态切换 cProps */
const cProps = computed(() =>
  editForm.nodeType === 'SYSTEM' ? onlySystemRootProps : normalProps
)

/** SYSTEM 时不允许清空；其余允许 */
const cascaderClearable = computed(() => editForm.nodeType !== 'SYSTEM')
const cascaderPlaceholder = computed(() =>
  cascaderClearable.value ? '选择起点（根层可留空）' : '请选择起点（必选）'
)

/** 切换类型时重置选择，避免旧值与新规则不匹配 */
watch(() => editForm.nodeType, () => { rootPath.value = null })


// 估算层级：父路径长度 + 层序号
const baseLevel = ref(0);
watch(editForm.parentId, () => {
  baseLevel.value = editForm.parentId?.length || 0;
});

const idNameMap = ref(new Map());
const idPidMap = ref(new Map());
const idCodeMap = ref(new Map());

function indexRows(rows = []) {
  rows.forEach((r) => {
    const id = r.id;
    const pid = r.pid ?? r.parentId ?? 0;
    idNameMap.value.set(id, r.name ?? "");
    idPidMap.value.set(id, pid);
    idCodeMap.value.set(id, r.code ?? "");
  });
}

const idPathMap = ref(new Map());

function segOf(id) {
  const name = idNameMap.value.get(id) ?? "";
  const code = idCodeMap.value.get(id) ?? "";
  return code ? `${name}(${code})` : name;
}

function computePathForId(id, seen = new Set()) {
  if (idPathMap.value.has(id)) return idPathMap.value.get(id);
  if (seen.has(id)) {
    // 防循环
    const self = segOf(id);
    idPathMap.value.set(id, self);
    return self;
  }
  seen.add(id);

  const pid = idPidMap.value.get(id);
  const self = segOf(id);
  if (pid && pid !== 0) {
    const p = computePathForId(pid, seen);
    const full = p ? `${p} / ${self}` : self;
    idPathMap.value.set(id, full);
    return full;
  } else {
    idPathMap.value.set(id, self);
    return self;
  }
}

function rebuildIdPathMap() {
  idPathMap.value.clear();
  for (const id of idNameMap.value.keys()) computePathForId(id);
}

function updatePathsFor(rows = []) {
  // 增量
  rows.forEach((r) => computePathForId(r.id));
}

// 对外：传 row 或 id，取完整路径
function fullPathOf(rowOrId) {
  const id = typeof rowOrId === "object" ? rowOrId.id : rowOrId;
  return idPathMap.value.get(id) || "";
}

// 自动把 Map 变成普通对象（{[id]: name}）
const id2name = computed(() => {
  const obj = {};
  for (const [k, v] of idNameMap.value) obj[k] = v ?? "";
  return obj;
});

async function fetchByParent(parentId = 0) {
  const params = {
    pageNum: q.pageNum,
    pageSize: 1000,
    parentId,
    orderByColumn: q.orderByColumn,
    isAsc: q.isAsc,
  };
  const resp = await listSbtz(params);
  const rows = (resp?.rows || []).map(adaptRow);

  indexRows(rows);

  // 注意：返回 plain 对象，避免外部拿到 ref
  return { rows, id2name: id2name.value };
}

// 使用：父名称 = id2name[row.pid]
function parentNameOf(rowOrId) {
  const pid =
    typeof rowOrId === "object"
      ? rowOrId.pid ?? rowOrId.parentId
      : idPidMap.value.get(rowOrId);
  return pid ? (id2name.value ? id2name.value[pid] : id2name[pid]) : "";
}

async function fetchAllForMother(motherId) {
  // 例如先把母设备与其所有子孙拉全（你已有 BFS）
  const rows = await fetchAllDescendantsIncludingRoot(motherId); // 自己的函数：包含母机和全部子孙
  indexRows(rows); // 1) 写入 id->name/pid/code
  rebuildIdPathMap(); // 2) 统一生成 id->完整路径
  // 之后任何地方：
  // fullPathOf(someId)  或 fullPathOf(row)
}

async function ensureIndexedById(id) {
  if (!id || idNameMap.value.has(id)) return;
  const res = await getSbtz(id); 
  if (res?.data) indexRows([adaptRow(res.data)]);
}

async function ensureAncestorsIndexedForId(id) {
  // 先确保当前节点本身在索引里（一般已在）
  await ensureIndexedById(id);

  // 沿着 pid 一路向上补齐祖先
  let cur = idPidMap.value.get(id);
  let guard = 0; // 防止意外循环
  while (cur && cur !== 0 && guard++ < 50) {
    await ensureIndexedById(cur);
    cur = idPidMap.value.get(cur); 
  }
}

async function backfillAncestorsForList(rows = []) {
  for (const r of rows) {
    await ensureAncestorsIndexedForId(r.id);
  }
}

function clearIndexes() {
  // ★ 可选：避免索引长期膨胀
  idNameMap.value.clear();
  idPidMap.value.clear();
  idCodeMap.value.clear();
  idPathMap.value.clear();
}

async function fetchMothers() {
  const params = {
    pageNum: q.pageNum, // ★ 与若依分页匹配
    pageSize: q.pageSize, // ★ 与若依分页匹配
    name: q.name || null,
    code: q.code || null,
    createTime: q.createDate || null,
    nodeType: "SYSTEM",
    orderByColumn: q.orderByColumn,
    isAsc: q.isAsc,
  };
  const resp = await listSbtz(params);
  const rows = (resp?.rows || []).map(adaptRow); 
  motherRows.value = rows;  
  total.value = Number(resp?.total);
  // total.value = Number(resp?.total || rows.length);

  // （推荐）新查询/翻页时重建索引，只保留当前页 + 祖先
  clearIndexes();  
  indexRows(rows);
  await backfillAncestorsForList(rows);
  rebuildIdPathMap();

  // 触发一次渲染，确保 parentName/fullPath 刷新
  motherRows.value = [...motherRows.value];
}

function parentName(rowOrId) {
  const id = typeof rowOrId === "object" ? rowOrId.id : rowOrId;
  const pid =
    (typeof rowOrId === "object"
      ? rowOrId.pid ?? rowOrId.parentId
      : idPidMap.value.get(id)) ?? 0;

  if (!pid) return "—"; // 根或无父
  return idNameMap.value.get(pid) || "—"; // 来自索引的父名称
}

function parentCode(rowOrId) {
  const id = typeof rowOrId === "object" ? rowOrId.id : rowOrId;
  const pid =
    (typeof rowOrId === "object"
      ? rowOrId.pid ?? rowOrId.parentId
      : idPidMap.value.get(id)) ?? 0;

  if (!pid) return ""; // 根层无父
  return idCodeMap.value.get(pid) || "";
}

function useNum(total, aval) {
  return total - aval;
}

async function fetchAllDescendantsIncludingRoot(rootId) {
  const seen = new Set();
  const queue = [rootId];
  const out = []; // 包含根

  while (queue.length) {
    const pid = queue.shift();
    if (seen.has(pid)) continue;
    seen.add(pid);

    if (pid === rootId) {
      // 根本身的详情（如果母设备列表里已有，可以直接用那行替代）
      const rootRow = motherRows.value.find((r) => r.id === rootId);
      if (rootRow) out.push(rootRow);
      else {
        const one = await getSbtz(rootId);
        if (one?.data) out.push(adaptRow(one.data));
      }
    }

    const resp = await listSbtz({ parentId: pid, pageNum: 1, pageSize: 1000 });
    const rows = (resp?.rows || []).map(adaptRow);
    out.push(...rows);
    rows.forEach((r) => queue.push(r.id));
  }

  // 去重（保险）
  const uniq = [];
  const mark = new Set();
  for (const r of out)
    if (!mark.has(r.id)) {
      mark.add(r.id);
      uniq.push(r);
    }
  return uniq;
}

/** 可按页面动态切换父ID */
const editParentId = ref(null);

function onNodeClick(data) {}
function onRowDblclick(row) {}
function onLoaded(info) {}

async function openChildrenDialog(row) {
  if (dblBusy.value) return;
  dblBusy.value = true;
  try {
    motherCurrent.value = row;

    // ✅ 重置真正绑定到 FeatureTabsContainer 的那个 ref
    activeTab.value = "base";

    childrenVisible.value = true;
    dialogLoading.value = true;
    await loadAllDescendants(row.id);
  } catch (e) {
    ElMessage.error("加载失败，请重试");
  } finally {
    dialogLoading.value = false;
    dblBusy.value = false;
  }
}

// ===== 弹窗（目录页 + 顶部导航）=====
const activeView = ref("home"); // 先显示目录
// const active     = ref<'base'|'group'|'usage'|'usageData'|'maintenance'>('base');

 
 

 
const parentPath = ref([]); // 级联 v-model（数组）
watch(parentPath, (arr) => {
  editForm.parentId =
    Array.isArray(arr) && arr.length ? arr[arr.length - 1] : 0; // 同步成后端要的父ID
});

function rebuildIndex(list) {
  const m = new Map();
  for (const r of list) m.set(r.id, r);
  idMap.value = m;
}

function pathText(row) {
  if (!row) return "";
  const parts = [];
  let cur = row;
  while (cur) {
    parts.unshift(`${cur.name ?? ""}(${cur.code ?? ""})`);
    if (cur.pid == null) break;
    cur = idMap.value.get(cur.pid);
  }
  return parts.join(" / ");
}

const rowClassName = () => "";

/** 详情 */
function openDetail(row) {
  detailRow.value = row;
  detailVisible.value = true;
}

/** 新增子设备 / 修改 */
function openAddChild(row) {
  // 任选其一或都用
  cascaderKey.value += 1    
  showCascader.value = false     
  nextTick();
  showCascader.value = true     
  editVisible.value = true

  console.log(row);
  editForm.id = null;  
  editForm.parentId = row?.id ?? null;
  editForm.nodeType = row?.nodeType ?? "MODULE";
  editForm.name = "";
  editForm.code = "";
  editForm.spareIds = [];
  editForm.remark = "";
  // 新增字段清空
  editForm.specification = "";
  editForm.model = "";
  editForm.material = "";
  editForm.power = "";
  editForm.brand = "";
  editForm.manufacturer = "";
  editForm.isSpecial = null;
  editForm.status = null;
  editForm.deptId = null;

  editForm.mokuai = "";
  editForm.area = "";
  editForm.designTemp = "";
  editForm.designPress = "";
  editForm.motorPower = "";
  editForm.mixingType = "";
  editForm.beType = "";
  editForm.unit = "台";
  editForm.quantity = 1;
  editForm.amount = null;
  editVisible.value = true;
}

async function openEdit(row) {
  editLoading.value = true;
  editVisible.value = true;
   
  // 任选其一或都用
  cascaderKey.value += 1    
  showCascader.value = false     
  nextTick();
  showCascader.value = true     
  editVisible.value = true

  editForm.id = row.id ?? null;
  editForm.parentId = row.pid ?? 0;
  editForm.nodeType = row.nodeType || "MODULE";
  editForm.name = row.name || "";
  editForm.code = row.code || "";
  editForm.remark = row.remark || "";

  editForm.specification = row.specification || "";
  editForm.model = row.model || "";
  editForm.material = row.material || "";
  editForm.power = row.power || "";
  editForm.brand = row.brand || "";
  editForm.manufacturer = row.manufacturer || "";
  editForm.isSpecial = row.isSpecial ?? null;
  editForm.status = row.status ?? null;
  editForm.deptId = row.deptId || null;

  editForm.mokuai = row.mokuai || "";
  editForm.area = row.area || "";
  editForm.designTemp = row.designTemp || "";
  editForm.designPress = row.designPress || "";
  editForm.motorPower = row.motorPower || "";
  editForm.mixingType = row.mixingType || "";
  editForm.beType = row.beType ?? "";
  editForm.unit = row.unit ?? null;
  editForm.quantity = row.quantity || null;
  editForm.amount = row.amount || null;

  // ✅ 关键：先清空，再用详情回填
  editForm.spareIds = [];

  // 建议用详情接口拿“当前已关联的备件集合” xxxxx
  const detail = await getSbtz(row.id)
    .then((r) => r?.data)
    .catch(() => null);
  const ids = (detail?.spareInfos || [])
    .map((s) => Number(s.id)) // ✅ 与 options 的 value 类型一致
    .filter((v) => Number.isFinite(v));

  // ✅ 确保 options 已加载，再赋值
  if (!spareLoaded.value) await getSpareList();
  await nextTick();
  editForm.spareIds = ids;

  // …你原有的禁选/父路径展开逻辑保持
  if (editForm.id) await buildBanIdsFor(editForm.id);
  else banIds.value = new Set();

  if (editForm.parentId) await ensureAncestorsIndexedForId(editForm.parentId);
  parentPath.value = await buildCascaderPath(editForm.parentId);
}

async function buildCascaderPath(targetId) {
  if (!targetId) return [];
  // 生成从根到目标的 id 链
  const chain = [];
  let cur = targetId,
    guard = 0;
  while (cur && guard++ < 100) {
    chain.unshift(cur);
    cur = idPidMap.value.get(cur) ?? 0;
  }
  // 让级联在懒加载模式下也能吃到链（无需手动展开，ElementPlus 会逐级触发 lazyLoad）
  return chain;
}

async function submitEdit() {
  await editRef.value.validate();
  if (editForm.id) {
    // 修改
    const payload = {
      nodeId: editForm.id,
      newParentId: editForm.parentId ?? 0,
      keepCode: false,
      override: {
        nodeType: editForm.nodeType,
        name: editForm.name,
        code: editForm.code ?? null,
        remark: editForm.remark || "",

        // 新增字段
        specification: editForm.specification || "",
        model: editForm.model || "",
        material: editForm.material || "",
        power: editForm.power || "",
        brand: editForm.brand || "",
        manufacturer: editForm.manufacturer || "",
        isSpecial: editForm.isSpecial ?? null,
        status: editForm.status ?? null,
        deptId: editForm.deptId || null,
        spareIds: Array.isArray(editForm.spareIds) ? editForm.spareIds : [],

        mokuai: editForm.mokuai || "",
        area: editForm.area || "",
        designTemp: editForm.designTemp || "",
        designPress: editForm.designPress || "",
        motorPower: editForm.motorPower || "",
        mixingType: editForm.mixingType || "",
        beType: editForm.beType || "",
        unit: editForm.unit || "台",
        quantity: editForm.quantity ?? null ,
        amount: editForm.amount ?? null,
      },
    };
    const resData = await rehomeRoute(payload);
    if (resData.code == 200) {
      ElMessage.success("修改成功");
    } else {
      ElMessage.success("修改失败");
    }

    fetchMothers();
    // 刷新弹窗里的“所有子孙”
    if (motherCurrent.value) await loadAllDescendants(motherCurrent.value.id);
    editVisible.value = false;
  } else {
    // 新增子节点（子设备）
    const layer = {
      nodeType: editForm.nodeType,
      name: editForm.name,
      code: editForm.code ?? null,
      remark: editForm.remark || "",

      // 新增字段
      specification: editForm.specification || "",
      model: editForm.model || "",
      material: editForm.material || "",
      power: editForm.power || "",
      brand: editForm.brand || "",
      manufacturer: editForm.manufacturer || "",
      isSpecial: editForm.isSpecial ?? null,
      status: editForm.status ?? null,
      deptId: editForm.deptId || null,
      spareIds: Array.isArray(editForm.spareIds) ? editForm.spareIds : [],
      mokuai: null,
      area: null,
      designTemp: null,
      designPress: null,
      motorPower: null,
      mixingType: null,
      beType: null,
      unit: null,
      quantity: null,
      amount: null,
    };
    const payload = {
      startParentId: editForm.parentId,
      reuseCode: false,
      layers: [layer],
    };
    const resData = await createRoute(payload);
    if (res.code == 200) {
      ElMessage.success("新增成功");
    } else {
      ElMessage.error("新增失败");
    }

    if (motherCurrent.value) await loadAllDescendants(motherCurrent.value.id);
    editVisible.value = false;
  }
}

/** 删除节点（母或子） */
async function removeNode(row) {
  try {
    await ElMessageBox.confirm(`确定删除【${row.name}】？`, "提示", {
      type: "warning",
    });
  } catch {
    return;
  }
  await delSbtz(row.id);
  ElMessage.success("删除成功");
  if (childrenVisible.value && motherCurrent.value) {
    await loadAllDescendants(motherCurrent.value.id);
  } else {
    await fetchMothers();
  }
}

/** label */
function labelByType(t) {
  switch ((t || "").toUpperCase()) {
    case "SYSTEM":
      return "系统";
    case "MACHINE":
      return "母设备";
    case "MODULE":
      return "子设备";
    case "COMPONENT":
      return "部件";
    case "PART":
      return "零件";
    default:
      return t || "—";
  }
}

/** ========== 父节点选择器（懒加载） ========== **/
const parentTreeData = ref([]); // 根层（parentId=0）的选项
const banIds = ref(new Set()); // 禁选集合：当前节点及其所有后代
const parentTreeProps = {
  value: "id",
  label: "label",
  children: "children",
  isLeaf: "isLeaf",
  disabled: "disabled",
  lazy: true,
  lazyLoad: loadParentNodes,
};

/** 把接口 rows 转成 TreeSelect 节点 */
function toOption(r) {
  const label = r.code ? `${r.name}(${r.code})` : r.name;
  const hasChildren =
    (r.childCount ??
      r.childrenCount ??
      r.subCount ??
      r.childrenNum ??
      r.childNum ??
      0) > 0;
  return {
    id: r.id,
    label,
    isLeaf: !hasChildren, // 没有子，就标记叶子；即便不准，也能继续懒加载
    disabled: banIds.value.has(r.id),
    children: [], // 懒加载时填充
  };
}

/** 懒加载：加载某节点的直接子 */
async function loadParentNodes(node, resolve) {
  try {
    const pid = node?.level ? node.data.id : 0; // level 为 0/undefined 代表根
    const resp = await listSbtz({ parentId: pid, pageNum: 1, pageSize: 1000 });
    const rows = (resp?.rows || []).map(adaptRow);
    // 建索引（供路径显示/后续使用）
    indexRows(rows);
    updatePathsFor(rows);
    const options = rows.map(toOption);
    resolve(options);
  } catch (e) {
    resolve([]);
  }
}

/** 可选：打开下拉时，如果还没加载根，就加载一次 */
async function onParentTreeVisible(visible) {
  if (!visible) return;
  if (!parentTreeData.value.length) {
    // 初始根
    const resp = await listSbtz({ parentId: 0, pageNum: 1, pageSize: 1000 });
    const rows = (resp?.rows || []).map(adaptRow);
    indexRows(rows);
    updatePathsFor(rows);
    parentTreeData.value = rows.map(toOption);
  }
}

/** 设为顶层（无父） */
function setAsTop() {
  editForm.parentId = 0;
}

/** 计算“禁选集合”：当前节点及其所有后代 */
async function buildBanIdsFor(currentId) {
  const set = new Set([currentId]);
  // BFS 拉全后代，仅拿 id 填集合
  const q = [currentId];
  while (q.length) {
    const pid = q.shift();
    const resp = await listSbtz({ parentId: pid, pageNum: 1, pageSize: 1000 });
    const rows = (resp?.rows || []).map(adaptRow);
    rows.forEach((r) => {
      if (!set.has(r.id)) {
        set.add(r.id);
        q.push(r.id);
      }
    });
  }
  banIds.value = set;
}

/** 确保祖先链已加载到树中：用于把当前 parentId 的路径展开出来（可选增强） */
async function expandChainTo(targetId) {
  if (!targetId) return;
  // 先保证 id/pid 索引齐全
  await ensureAncestorsIndexedForId(targetId);

  // 从 root 到 target 的 id 链
  const chain = [];
  let cur = targetId,
    guard = 0;
  while (cur && guard++ < 100) {
    chain.unshift(cur);
    cur = idPidMap.value.get(cur);
  }

  // 逐层展开：先确保根已载入
  if (!parentTreeData.value.length) {
    const resp = await listSbtz({ parentId: 0, pageNum: 1, pageSize: 1000 });
    const rows = (resp?.rows || []).map(adaptRow);
    indexRows(rows);
    updatePathsFor(rows);
    parentTreeData.value = rows.map(toOption);
  }

  // 逐层触发懒加载
  let siblings = parentTreeData.value;
  for (const id of chain) {
    let node = siblings.find((n) => n.id === id);
    if (!node) {
      // 不在当前 siblings 里，可能父层还没展开：先加载父层
      const pid = idPidMap.value.get(id) ?? 0;
      if (pid === 0) continue; // 根层已处理
      // 找父节点
      const parentNode = findNodeInTree(parentTreeData.value, pid);
      if (
        parentNode &&
        (!parentNode.children || parentNode.children.length === 0)
      ) {
        await new Promise((resolve) =>
          loadParentNodes({ level: 1, data: parentNode }, (kids) => {
            parentNode.children = kids;
            resolve();
          })
        );
      }
      node = parentNode?.children?.find((n) => n.id === id);
    }
    siblings = node?.children || [];
  }
}

/** 在已加载的树里找一个节点（辅助函数） */
function findNodeInTree(list, id) {
  const st = Array.isArray(list) ? [...list] : [];
  while (st.length) {
    const n = st.shift();
    if (!n) continue;
    if (n.id === id) return n;
    if (n.children?.length) st.unshift(...n.children);
  }
  return null;
}

function copyPath(row) {
  const text = fullPathOf(row);
  navigator.clipboard?.writeText(text);
  ElMessage.success("已复制路径：" + text);
}

/** ★ 拉取某母设备下的“所有后代（子孙）”：BFS 连续请求 parentId=xxx 直到叶子 */
async function loadAllDescendants(rootId) {
  const seen = new Set(); // 防重
  const queue = [rootId]; // 从母设备 id 开始
  const collected = []; // 平铺结果（不包括根节点）
  const pageSize = 1000; // 可按需调整

  while (queue.length) {
    const pid = queue.shift();
    if (seen.has(pid)) continue;
    seen.add(pid);

    const resp = await listSbtz({ parentId: pid, pageNum: 1, pageSize }); // 直系子
    // const rows = (resp?.rows || []).map(adaptRow);
    const rows = resp?.rows;

    for (const r of rows) {
      idMap.value.set(r.id, r);
      // 关键：把子设备也写进 idNameMap/idPidMap，供路径计算
      indexRows([r]);
      // 增量生成该节点的完整路径（会沿着 pid 递归向上）
      computePathForId(r.id);

      if (r.id !== rootId) collected.push(r);
      queue.push(r.id);
    }
  }

  // 你也可以在这里去重/排序：按 level 再按 name
  collected.sort(
    (a, b) =>
      (a.level || 0) - (b.level || 0) ||
      String(a.name).localeCompare(String(b.name))
  );

  descendants.value = collected;

  // 可选：一次性重建，保证所有 id 都有缓存路径
  rebuildIdPathMap();
}

/** 弹窗里“展开/收起全部”是语义功能位；这里给个占位（如要树形卡片再接入） */
function expandAllInDialog(_open) {
  ElMessage.info(
    "已按层级排序展示所有子孙。如果需要真正的树形展开/收起，可把卡片改为 Tree + Cards 混排。"
  );
}

/** 维修历史（入口保留，按你的后端实现） */
function openRepairHistory(row) {
  // 1) 跳路由：router.push({ name:'RepairHistory', query:{ nodeId: row.id }})
  // 2) 或弹窗表格：列出维修记录，并可新增/编辑/删除
  ElMessage.info(`打开【${row.name}】的维修历史（请接入后端接口）`);
}

const { proxy } = getCurrentInstance();
const { asset_node_type } = proxy.useDict("asset_node_type");
const { asset_node_status } = proxy.useDict("asset_node_status");
/** 导出（维持与你项目一致的导出接口） */
function handleExport() {
  // proxy.download(
  //   "sbtz/sbtz/export",
  //   { ...q, nodeType: "SYSTEM" },
  //   `sbtz_${new Date().getTime()}.xlsx`
  // );
  proxy.download(
    "sbtz/sbtz/excel",
    { ...q, nodeType: "SYSTEM" },
    `sbtz_${new Date().getTime()}.xlsx`
  );
  // proxy.download(
  //   "sbtz/sbtz/exportChain",
  //   { ...q, nodeType: "SYSTEM" },
  //   `mother_devices_${Date.now()}.xlsx`
  // );
}

// 顶部 import 保持不变

/** Affix 的滚动容器（字符串选择器），找不到则用 window */
const affixTarget = ref(undefined);

onMounted(() => {
  // 按你项目实际情况补充/调整候选
  const candidates = [
    ".app-main .el-scrollbar__wrap", // RuoYi + Element Plus 常见
    ".el-scrollbar__wrap",
    ".app-main",
  ];
  const found = candidates.find((sel) => document.querySelector(sel));
  // el-affix 的 target 需要字符串；undefined 则表示使用 window
  affixTarget.value = found || undefined;
});

function resetSearch() {
  q.name = "";
  q.code = "";
  q.createDate = null;
  q.pageNum = 1; // ★ 重置到第一页
  fetchMothers();
}
const spareLoaded = ref(false);

/** 备件列表（用于新增/编辑） */
async function getSpareList() {
  const { rows = [] } = await listSpare({ pageNum: 1, pageSize: 9999 });
  // ✅ 统一成 Number（如果你的 id 可能很大>2^53-1，就统一用 String，两端都用 String）
  spareListOptions.value = rows.map(({ id, name }) => ({
    value: Number(id),
    label: name,
  }));
  spareLoaded.value = true;
}

function parentPathText(pid) {
  if (!pid) return "—";
  // 先尝试已有完整路径
  const cached = idPathMap.value.get(pid);
  if (cached) return cached;

  // 没有则现场计算（依赖 idNameMap/idPidMap）
  computePathForId(pid);
  return idPathMap.value.get(pid) || idNameMap.value.get(pid) || "—";
}

// —— 把母设备 + 子孙扁平列表 → 树（数组，只有一个根）——
function buildDeviceTree(rootRow, flat = []) {
  if (!rootRow) return [];
  const map = new Map();
  const mk = (r) => {
    // const label = r.code ? `${r.name}(${r.code})` : r.name;
    const label = r.name;
    return map
      .set(r.id, {
        id: r.id,
        label,
        _rawName: r.name || "",
        _rawCode: r.code || "",
        childCount: r.childCount ?? 0,
        typeLabel: labelByType(r.nodeType || r.type),
        children: [],
      })
      .get(r.id);
  };
  mk(rootRow);
  flat.forEach(mk);
  flat.forEach((r) => {
    const pid = r.pid ?? r.parentId ?? 0;
    if (pid && map.has(pid)) map.get(pid).children.push(map.get(r.id));
  });
  return [map.get(rootRow.id)];
}

const typeOptions = ref([]);
async function getTypeList() {
  const res = await listSbtype(loadAllParams);
  typeOptions.value = (res?.rows ?? []).map(
    ({ typeCode: value, displayName: label }) => ({ value, label })
  );
}

// ====== 列头筛选状态（统一管理）======
const filters = reactive({
  // 下拉+自定义：display 供 UI 绑定；text 是规范化后的文本；id 用于精确匹配父节点
  systemName: { display: null, text: "", id: null },
  systemCode: { display: null, text: "", id: null },
  muName: { display: null, text: "", id: null },
  MuCode: { display: null, text: "", id: null },
  pathKw: "",
  nameCodeKw: "",
});

// 选项池与加载态
const options = reactive({
  systemName: [],
  systemCode: [], // ★ 新增
  muName: [],
  MuCode: [],
});

const loading = reactive({
  systemName: false,
  systemCode: false, // ★ 新增
  muName: false,
  MuCode: false,
});

// 远程下拉：每列可接不同后端接口
const remoteOptions = {
  async systemName(query) {
    loading.systemName = true;
    try {
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 50,
        name: query || null,
        parentId: 0,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      options.systemName = rows.map((r) => ({
        value: r.id,
        id: r.id,
        text: r.name || "",
        label: r.code ? `${r.name}(${r.code})` : r.name || "",
      }));
    } finally {
      loading.systemName = false;
    }
  },
  async parentCode(query) {
    loading.parentCode = true;
    try {
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 50,
        code: query || null,
        parentId: 0,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      const codes = Array.from(
        new Set(rows.map((r) => r.code).filter(Boolean))
      );
      options.parentCode = codes.map((c) => ({ value: c, label: c, text: c }));
    } finally {
      loading.parentCode = false;
    }
  },
  async MuName(query) {
    loading.muName = true;
    try {
      const q = (query || "").trim();
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 50,
        nodeType: "SYSTEM", // 只搜母设备
        name: q || null,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      options.muName = rows.map((r) => ({
        value: r.id,
        id: r.id,
        text: r.name || "",
        label: r.name,
        // label: r.code ? `${r.name}(${r.code})` : r.name || "",
      }));
    } finally {
      loading.muName = false;
    }
  },
  async MuCode(query) {
    loading.MuCode = true;
    try {
      const q = (query || "").trim();
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 50,
        nodeType: "SYSTEM", // 只搜母设备
        code: q || null,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      options.MuCode = rows.map((r) => ({
        value: r.id,
        id: r.id,
        text: r.code || "",
        label: r.code,
      }));
    } finally {
      loading.MuCode = false;
    }
  },
};

const SYSCODE_MIN_CHARS = 1; // 可按需设 1~2
const systemCodeNoDataText = computed(() =>
  typeof filters.systemCode.display === "string" &&
  filters.systemCode.display.trim().length > 0 &&
  filters.systemCode.display.trim().length < SYSCODE_MIN_CHARS
    ? `请输入至少 ${SYSCODE_MIN_CHARS} 个字符开始搜索`
    : "无匹配数据"
);

remoteOptions.systemCode = async function (query) {
  const q = (query || "").trim();
  if (q && q.length < SYSCODE_MIN_CHARS) {
    options.systemCode = [];
    return;
  }
  loading.systemCode = true;
  try {
    const resp = await listSbtz({
      pageNum: 1,
      pageSize: 30,
      parentId: 0, // 根层系统
      code: q || null, // 按系统“编码”搜父
    });
    const rows = (resp?.rows || []).map(adaptRow);
    options.systemCode = rows.map((r) => ({
      value: r.id,
      id: r.id, // 父系统 id（后续筛选用）
      text: r.code || "",
      label: r.code,
      // label: r.code ? `${r.code}（${r.name || '-' }）` : (r.name || '-'),
    }));
  } finally {
    loading.systemCode = false;
  }
};

function onSystemCodeChange(val) {
  if (typeof val === "string") {
    filters.systemCode.text = val.trim();
    filters.systemCode.id = null;
  } else {
    filters.systemCode.text = (val && (val.text || val.label)) || "";
    filters.systemCode.id = (val && val.id) || null;
  }
  scheduleSearch();
}
function onSystemCodeClear() {
  filters.systemCode = { display: null, text: "", id: null };
  scheduleSearch();
}
function onSystemCodeVisible(v) {
  if (!v) return;
  if (!Array.isArray(options.systemCode) || options.systemCode.length === 0) {
    remoteOptions.systemCode(""); // 首屏预取
  }
}

// 中文名 -> Tag 风格（Element Plus 的四种：success / warning / danger / info）
function typeTagType(label) {
  const map = {
    系统: "success",
    母设备: "primary",
    子设备: "warning",
    部件: "warning",
    零件: "info",
  };
  return map[label] || "info";
}

/**
 * 从后端返回的 data.nodeType 读取类型中文名；
 * 若没有（极端兜底），按层级 node.level 粗略推断。
 */
function typeLabelOf(node, data) {
  const code = data && (data.nodeType || data.node_type);
  if (code && TYPE_LABELS[code]) return TYPE_LABELS[code];

  // —— 兜底：根据级联层级推断（后端没给 nodeType 时才会走到这）——
  // level: 1=系统/根；2=母设备；3=子设备；4=部件；5+=零件
  const lv = node && typeof node.level === "number" ? node.level : 0;
  if (lv <= 1) return "系统";
  if (lv === 2) return "母设备";
  if (lv === 3) return "子设备";
  if (lv === 4) return "部件";
  return "零件";
}

function onMuCodeChange(val) {
  if (typeof val === "string") {
    filters.MuCode.text = val.trim();
    filters.MuCode.id = null;
  } else {
    filters.MuCode.text = (val && (val.text || val.label)) || "";
    filters.MuCode.id = (val && val.id) || null;
  }
  scheduleSearch();
}
function onMuCodeClear() {
  filters.MuCode = { display: null, text: "", id: null };
  scheduleSearch();
}
function onMuCodeVisible(v) {
  if (!v) return;
  if (!Array.isArray(options.MuCode) || options.MuCode.length === 0) {
    remoteOptions.MuCode(""); // 预取一批
  }
}

function onMuNameChange(val) {
  if (typeof val === "string") {
    filters.muName.text = val.trim();
    filters.muName.id = null;
  } else {
    filters.muName.text = (val && (val.text || val.label)) || "";
    filters.muName.id = (val && val.id) || null;
  }
  scheduleSearch();
}
function onMuNameClear() {
  filters.muName = { display: null, text: "", id: null };
  scheduleSearch();
}
function onMuNameVisible(v) {
  if (!v) return;
  if (!Array.isArray(options.muName) || options.muName.length === 0) {
    // 这里不需要 await，异步加载候选即可
    remoteOptions.MuName("");
  }
}

const MUNAME_MIN_CHARS = 2;
const muNoDataText = computed(() =>
  filters.muName?.display &&
  typeof filters.muName.display === "string" &&
  filters.muName.display.trim().length < MUNAME_MIN_CHARS
    ? `请输入至少 ${MUNAME_MIN_CHARS} 个字符开始搜索`
    : "无匹配数据"
);

// ====== 远程下拉优化：系统名称 ======
const sysnameCache = new Map(); // key: queryLower -> options[]
let sysnameTimer = null;
let sysnameSeq = 0; // 用于丢弃过期请求的竞态序号

remoteOptions.systemName = function (query) {
  const q = (query || "").trim();
  // ✅ 最小输入长度限制（空字符串用于“首屏预取”）
  if (q && q.length < SYSNAME_MIN_CHARS) {
    options.systemName = [];
    return;
  }

  // ✅ 简单去抖：避免用户每敲一个字符都打一次接口
  if (sysnameTimer) clearTimeout(sysnameTimer);
  const mySeq = ++sysnameSeq;
  loading.systemName = true;

  sysnameTimer = window.setTimeout(async () => {
    try {
      const key = q.toLowerCase();

      // ✅ 命中缓存直接返回
      if (sysnameCache.has(key)) {
        if (mySeq === sysnameSeq) options.systemName = sysnameCache.get(key);
        return;
      }

      // ✅ 发请求（注意限制 pageSize）
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 30, // 控制体量，首屏更快
        parentId: 0, // 根层（你的需求）
        name: q || null,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      const opts = rows.map((r) => ({
        value: r.id,
        id: r.id,
        text: r.name || "",
        label: r.code ? `${r.name}(${r.code})` : r.name || "",
      }));

      // ✅ 写入缓存，后续相同关键词秒出
      sysnameCache.set(key, opts);

      // ✅ 仅当这是最新一次调用时才落地结果（丢弃过期响应）
      if (mySeq === sysnameSeq) options.systemName = opts;
    } finally {
      // 只有“自己是最新一次调用”时才关 loading，避免闪烁
      if (mySeq === sysnameSeq) loading.systemName = false;
    }
  }, 250); // 250ms 去抖（可调）
};

// ====== Enter/选择回调（关键）======
function onSystemNameChange(val) {
  if (typeof val === "string") {
    // 下拉没有，用户按了 Enter：把“输入字符串”作为自定义名称
    filters.systemName.text = val.trim();
    filters.systemName.id = null;
  } else {
    // 选择了候选项
    filters.systemName.text = (val && (val.text || val.label)) || "";
    filters.systemName.id = (val && val.id) || null;
  }
  scheduleSearch(); // 统一触发查询（见下）
}

function onSystemNameClear() {
  filters.systemName = { display: null, text: "", id: null };
  scheduleSearch();
}

function onParentCodeChange(val) {
  if (typeof val === "string") filters.parentCode.text = val;
  else
    filters.parentCode.text =
      (val && (val.text || val.value || val.label)) || "";
  scheduleSearch();
}
function onParentCodeClear() {
  filters.parentCode = { display: null, text: "" };
  scheduleSearch();
}

const SYSNAME_MIN_CHARS = 2; // ✅ 输入至少 2 个字符才发请求（可调）
const sysnameNoDataText = computed(() =>
  filters.systemName?.display &&
  typeof filters.systemName.display === "string" &&
  filters.systemName.display.trim().length < SYSNAME_MIN_CHARS
    ? `请输入至少 ${SYSNAME_MIN_CHARS} 个字符开始搜索`
    : "无匹配数据"
);

// 打开下拉时，如果还没数据就预取一批（热门/TopN）
async function onSystemNameVisible(v) {
  if (!v) return;
  if (options.systemName.length) return;
  // 预取根层 TopN：不带关键词，给用户一个“首屏可见”的反馈
  await remoteOptions.systemName(""); // 会命中缓存/限流策略，见下
}

// ====== 搜索模式：统一接口 或 分列接口交集 ======
const SEARCH_MODE = "federated"; // 'unified' | 'federated'

// 每列的数据源函数：返回一个“id 集合”（Set<number>）
const FILTER_SOURCES = {
  async systemName(values) {
    // 1) 选中了具体父（精确：取其直系子）
    if (values && values.id) {
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 1000,
        parentId: values.id,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      return new Set(rows.map((r) => r.id));
    }
    // 2) 自定义名称（按父名称模糊 → 汇总这些父的直系子）
    const text = values && values.text ? values.text.trim() : "";
    if (!text) return new Set();

    // （这里就是“把输入内容传递给后端进行搜索”的地方）
    const parentResp = await listSearchData({
      pageNum: 1,
      pageSize: 1000,
      name: text,
      nodeType: "SYSTEM",
    });
    const parents = (parentResp?.rows || []).map(adaptRow);
    if (!parents.length) return new Set();

    const childIds = new Set();
    for (const p of parents) {
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 1000,
        parentId: p.id,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      rows.forEach((r) => childIds.add(r.id));
    }
    return childIds;
  },
  // ★ 新增：按“父系统编码”→ 找到父ID → 汇总这些父的直系子（母设备）ID
  async systemCode(values) {
    // 精确选中项（含 id）时，等价于直接查这个父ID的直系子
    const ids = [];
    if (values && values.id) {
      ids.push(values.id);
    } else if (values && values.text) {
      // 模糊编码 → 根层父系统 id[]
      const resp = await listSbtz({
        pageNum: 1,
        pageSize: 1000,
        parentId: 0,
        code: values.text.trim(),
      });
      const rows = (resp?.rows || []).map(adaptRow);
      rows.forEach((r) => ids.push(r.id));
    }
    if (!ids.length) return new Set();

    // 汇总这些父系统的直系子（母设备）
    const childIds = new Set();
    for (const pid of ids) {
      const r = await listSbtz({
        pageNum: 1,
        pageSize: 1000,
        parentId: pid,
        nodeType: "SYSTEM",
      });
      const rows = (r?.rows || []).map(adaptRow);
      rows.forEach((row) => childIds.add(row.id));
    }
    return childIds;
  },
  // ✅ 母设备（只命中母设备本身，不取子）
  async muName(values) {
    // 选中具体项：返回该母设备 id
    if (values && values.id) return new Set([values.id]);

    // 自定义文本：按“母设备名称/编码”模糊命中母设备自身
    const text = values && values.text ? values.text.trim() : "";
    if (!text) return new Set();
    const resp = await listSbtz({
      pageNum: 1,
      pageSize: 1000,
      nodeType: "SYSTEM",
      // 视后端而定：若 name 与 code 不能同传，可只传 name:text，
      // 或用你已有的 nameCodeLike；这里给出通用写法：
      name: text,
    });
    const rows = (resp?.rows || []).map(adaptRow);
    return new Set(rows.map((r) => r.id));
  },

  async muCode(values) {
    if (values && values.id) return new Set([values.id]); // 精确选中
    const text = values && values.text ? values.text.trim() : "";
    if (!text) return new Set();
    const resp = await listSbtz({
      pageNum: 1,
      pageSize: 1000,
      nodeType: "SYSTEM",
      code: text,
    });
    const rows = (resp?.rows || []).map(adaptRow);
    return new Set(rows.map((r) => r.id));
  },
  async pathKw(text) {
    if (!text) return new Set();
    const resp = await listSbtz({ pageNum: 1, pageSize: 1000 });
    const rows = (resp?.rows || []).map(adaptRow);
    indexRows(rows);
    updatePathsFor(rows);
    const kw = (text || "").toLowerCase();
    return new Set(
      rows
        .filter((r) => (fullPathOf(r) || "").toLowerCase().includes(kw))
        .map((r) => r.id)
    );
  },
  async nameCodeKw(text) {
    if (!text) return new Set();
    const resp = await listSbtz({
      pageNum: 1,
      pageSize: 1000,
      name: text,
      code: text,
    });
    const rows = (resp?.rows || []).map(adaptRow);
    return new Set(rows.map((r) => r.id));
  },
};

// 可选：没有批量详情接口时，用并发 getSbtz 兜底
async function fetchByIds(ids) {
  if (!ids || !ids.length) return [];
  const out = [];
  const pool = 10;
  for (let i = 0; i < ids.length; i += pool) {
    const slice = ids.slice(i, i + pool);
    const res = await Promise.all(
      slice.map((id) =>
        getSbtz(id)
          .then((r) => adaptRow(r.data))
          .catch(() => null)
      )
    );
    out.push(...res.filter(Boolean));
  }
  return out;
}

// 如果后端支持一次传多个父ID，请改为 true，并确认参数名（这里用 parentIds）
const SUPPORTS_PARENT_IDS = false;

// 根层父系统的模糊查，拿到 id 数组（父 name → pid[]）
async function searchParentIdsByNameLike(text) {
  const kw = (text || "").trim();
  if (!kw) return [];
  const resp = await listSearchData({
    pageNum: 1,
    pageSize: 1000,
    parentId: 0, // 只查根层系统
    name: kw,
  });
  const rows = (resp?.rows || []).map(adaptRow);
  return rows.map((r) => r.id);
}

async function searchParentIdsByCodeLike(text) {
  const kw = (text || "").trim();
  if (!kw) return [];
  const resp = await listSearchData({
    pageNum: 1,
    pageSize: 1000,
    parentId: 0, // 根层系统
    code: kw,
  });
  const rows = (resp?.rows || []).map(adaptRow);
  return rows.map((r) => r.id);
}

// 当后端不支持 parentIds 数组时：按每个 pid 并发请求，合并并做前端分页
async function fetchMotherByParentsFallback(parentIds, baseParams) {
  // 取足够的条数以便最终分页切片
  const need = q.pageNum * q.pageSize;
  const perPageSize = Math.max(need, 50);

  const jobs = parentIds.map((pid) =>
    listSbtz({
      ...baseParams,
      parentId: pid,
      pageNum: 1,
      pageSize: perPageSize,
    })
      .then((r) => ({
        rows: (r?.rows || []).map(adaptRow),
        total: Number(r?.total || 0),
      }))
      .catch(() => ({ rows: [], total: 0 }))
  );

  const parts = await Promise.all(jobs);

  // 合并去重
  const merged = [];
  let total = 0;
  const seen = new Set();
  for (const part of parts) {
    total += part.total;
    for (const row of part.rows) {
      if (seen.has(row.id)) continue;
      seen.add(row.id);
      merged.push(row);
    }
  }

  // 可选排序：更新时间倒序，其次名称
  merged.sort(
    (a, b) =>
      new Date(b.updateTime || 0) - new Date(a.updateTime || 0) ||
      String(a.name).localeCompare(String(b.name))
  );

  // 最终分页切片
  const start = (q.pageNum - 1) * q.pageSize;
  const end = start + q.pageSize;
  return { rows: merged.slice(start, end), total };
}

// 统一触发的搜索
async function runSearch() {
  if (SEARCH_MODE === "unified") {
    // 1) 先解析父系统条件 -> parentIds

    let parentIdsFromName = [];
    if (filters.systemName.id) {
      parentIdsFromName = [filters.systemName.id];
    } else if (filters.systemName.text) {
      parentIdsFromName = await searchParentIdsByNameLike(
        filters.systemName.text
      );
    }

    let parentIdsFromCode = [];
    if (filters.systemCode.id) {
      parentIdsFromCode = [filters.systemCode.id];
    } else if (filters.systemCode.text) {
      parentIdsFromCode = await searchParentIdsByCodeLike(
        filters.systemCode.text
      );
    }

    let parentIds = [];
    if (parentIdsFromName.length && parentIdsFromCode.length) {
      const s = new Set(parentIdsFromCode);
      parentIds = parentIdsFromName.filter((x) => s.has(x));
    } else {
      parentIds = parentIdsFromName.length
        ? parentIdsFromName
        : parentIdsFromCode;
    }

    // 2) 解析母设备条件
    const muId = filters.muName.id || null;
    const codeId = filters.MuCode.id || null;
    const muText = (filters.muName.text || "").trim();
    const muCode = (filters.MuCode.text || "").trim();
    // 3) 统一的基础参数（除父ID外的其它条件）
    const baseParams = {
      nodeType: "SYSTEM",
      ...(muId
        ? { id: muId }
        : {
            name: muText || q.name || null,
            code: muCode || q.code || null,
          }),
      pathLike: filters.pathKw || null,
      nameCodeLike: filters.nameCodeKw || null,
      createTime: q.createDate || null,
    };
    // 4) 根据 parentIds 的数量选择查询策略
    if (parentIds.length === 0) {
      // 无父过滤：一次请求
      const resp = await listSearchData({
        ...baseParams,
        pageNum: q.pageNum,
        pageSize: q.pageSize,
        orderByColumn: q.orderByColumn,
        isAsc: q.isAsc,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      await afterRowsArrived(rows, resp?.total ?? rows.length);
      return;
    }

    if (parentIds.length === 1) {
      // 单个父ID：直接带 parentId
      const resp = await listSbtz({
        ...baseParams,
        parentId: parentIds[0],
        pageNum: q.pageNum,
        pageSize: q.pageSize,
        orderByColumn: q.orderByColumn,
        isAsc: q.isAsc,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      await afterRowsArrived(rows, resp?.total ?? rows.length);
      return;
    }

    // 多个父ID
    if (SUPPORTS_PARENT_IDS) {
      // ✅ 如果你的后端支持多个父ID参数（数组或逗号串），用这一段：
      const resp = await listSbtz({
        ...baseParams,
        parentIds, // 如需改为字符串：parentIds: parentIds.join(',')
        pageNum: q.pageNum,
        pageSize: q.pageSize,
        orderByColumn: q.orderByColumn,
        isAsc: q.isAsc,
      });
      const rows = (resp?.rows || []).map(adaptRow);
      await afterRowsArrived(rows, resp?.total ?? rows.length);
      return;
    } else {
      // ❗后端不支持多个父ID：按父ID并发查并前端合并分页
      const { rows, total } = await fetchMotherByParentsFallback(
        parentIds,
        baseParams
      );
      await afterRowsArrived(rows, total);
      return;
    }
  }

  // 分列接口：各列查候选 → 交集 → 拉详情
  const jobs = [];

  // ★ 加上母设备（名称/编码）
  if (filters.muName.id || filters.muName.text) {
    jobs.push(
      FILTER_SOURCES.muName({
        id: filters.muName.id,
        text: filters.muName.text,
      })
    );
  }

  if (filters.MuCode.id || filters.MuCode.text) {
    jobs.push(
      FILTER_SOURCES.muCode({
        id: filters.MuCode.id,
        text: filters.MuCode.text,
      })
    );
  }

  if (filters.systemName.id || filters.systemName.text) {
    jobs.push(
      FILTER_SOURCES.systemName({
        id: filters.systemName.id,
        text: filters.systemName.text,
      })
    );
  }
  if (filters.systemCode.id || filters.systemCode.text) {
    jobs.push(
      FILTER_SOURCES.systemCode({
        id: filters.systemCode.id,
        text: filters.systemCode.text,
      })
    );
  }

  if (filters.pathKw) jobs.push(FILTER_SOURCES.pathKw(filters.pathKw));
  if (filters.nameCodeKw)
    jobs.push(FILTER_SOURCES.nameCodeKw(filters.nameCodeKw));

  if (!jobs.length) {
    await fetchMothers(); // 无列头过滤，回退到你的原查询（分页等保持一致）
    return;
  }

  const sets = await Promise.all(jobs);
  let inter = sets.shift() || new Set();
  for (const s of sets) inter = new Set([...inter].filter((x) => s.has(x)));

  const ids = [...inter];
  const rows = await fetchByIds(ids);
  afterRowsArrived(rows, rows.length);
}

// 统一后处理（索引/路径/刷新 UI）
async function afterRowsArrived(rows, totalLike) {
  motherRows.value = rows;
  total.value = Number(totalLike ?? rows.length);

  clearIndexes();
  indexRows(rows);
  await backfillAncestorsForList(rows);
  rebuildIdPathMap();

  motherRows.value = [...motherRows.value];
}

// 文本输入列：轻微防抖自动查
let thTimer;
function scheduleSearch() {
  if (thTimer) clearTimeout(thTimer);
  thTimer = window.setTimeout(runSearch, 300);
}

getDeptTree();
getTypeList();
onMounted(async () => {
  await getSpareList();
  // await fetchMothers();
  await runSearch();
});
</script>

<style scoped>
.mb12 {
  margin-bottom: 12px;
}
.flex {
  display: flex;
}
.items-center {
  align-items: center;
}
.cell-flex {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  gap: 8px;
}
.cell-left {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.cell-left .name {
  font-weight: 600;
  /* white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis; */
}
.cell-right {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.one-line {
  display: inline-block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.children-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

/* 2 列卡片布局 */
.card-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
}
@media (max-width: 720px) {
  .card-grid {
    grid-template-columns: 1fr;
  }
}
.child-card .card-hd {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.child-card .hd-left {
  display: inline-flex;
  gap: 6px;
  align-items: center;
  min-width: 0;
}
.child-card .card-name {
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 420px;
}
.child-card .card-body {
  display: grid;
  gap: 6px;
}
.child-card .kv .k {
  color: #909399;
}
.child-card .kv .v {
  color: #303133;
}
.child-card .card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}

/* 路径文本：默认一行，空间不够时在自身容器内换行，不影响右侧标签 */
.path-text {
  display: inline-block;
  max-width: 100%;
  /* 允许自动换行（长英文/长数字也能断） */
  white-space: normal;
  overflow-wrap: anywhere;
  word-break: break-word;
  line-height: 1.4;
}

/* 若“路径”与右侧标签同在一格横向布局时，保证标签不被挤压 */
.cell-flex {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  gap: 8px;
}

/* 左侧文本区域必须允许收缩，否则会把右侧标签顶走 */
.cell-left {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0; /* ★ 关键：允许左侧文本收缩 */
}

/* 右侧标签固定宽，不被文本挤压 */
.cell-right {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0; /* ★ 关键：标签不收缩 */
}

/* 让 el-dialog 头部留白，自己画顶栏 */
.equip-dialog :deep(.el-dialog__header) {
  padding: 0;
  border-bottom: none;
}

.topbar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 16px 0;
  background: #2f3a49;
  color: #e8eef5;
  position: sticky;
  top: 0;
  z-index: 1;
  border-top-left-radius: 8px;
  border-top-right-radius: 8px;
}

.topbar .logo {
  font-weight: 700;
  letter-spacing: 1px;
  margin-right: 8px;
  color: #fff;
}

.tabs {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 0 0 10px; /* 给 slider 留位置 */
  flex: 1;
}

.tab {
  appearance: none;
  border: 0;
  background: transparent;
  color: #c9d3dc;
  padding: 8px 14px;
  font-size: 14px;
  cursor: pointer;
  border-radius: 8px;
  transition: color 0.15s ease, background-color 0.15s ease;
  flex: 1 1 0; /* 均分宽度 */
  text-align: center;
}
.tab:hover {
  color: #ffffff;
  background: rgba(255, 255, 255, 0.06);
}
.tab.active {
  color: #ffffff;
}

.slider {
  position: absolute;
  left: 0;
  bottom: 0;
  height: 3px;
  background: #8aaef5;
  border-radius: 3px;
  transition: transform 0.25s ease;
}

.content-wrap {
  background: #f5f7fa;
  padding: 16px;
}

.mb12 {
  margin-bottom: 12px;
}

/* 内容卡片：顶边与 active tab 的“白色下边线”接壤 */
.panel {
  background: #fff;
  border: 1px solid #e5e7eb;
  border-radius: 0 8px 8px 8px;
  margin-top: -1px;
  padding: 16px;
  background-color: #accdfc;
}

.inputWidth {
  width: 220px;
}

.th-filter {
  display: grid;
  gap: 6px;
  align-items: center;
  justify-items: start;
  padding: 6px 6px 8px;
}
.th-filter > span {
  font-size: 12px;
  color: #606266;
  line-height: 1;
}
.th-filter.is-filtered > span {
  color: #3e8cf0;
  font-weight: 600;
}
.th-filter :deep(.el-input__wrapper),
.th-filter :deep(.el-select .el-input__wrapper) {
  box-shadow: none;
  border: 1px solid #e5e7eb;
  border-radius: 6px;
  padding: 0 8px;
  min-height: 28px;
}
.th-filter :deep(.is-focus) {
  border-color: #3e8cf0;
}
/* 1) 让每个节点行容器占满整行 */
.asset-tree :deep(.el-tree-node__content) {
  width: 100% !important;
  box-sizing: border-box;
}

/* 2) 覆盖任何 max-content/auto，强制 node-line 占满并做左右分栏 */
.asset-tree :deep(.node-line) {
  display: flex !important;
  align-items: center;
  gap: 8px;
  width: 100% !important; /* 关键 */
  padding: 0 6px; /* 给背景一点内边距 */
  border-radius: 8px; /* 背景圆角 */
}

/* 3) 左右区域：左侧可伸展，右侧贴右对齐 */
.asset-tree :deep(.node-left) {
  flex: 1 1 auto;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.asset-tree :deep(.node-right) {
  flex: 0 0 auto;
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

/* 4) 把“悬浮/选中”的背景直接加在 node-line 上（就能覆盖到右侧徽章） */
.asset-tree :deep(.el-tree-node__content:hover .node-line) {
  background: #f3f7ff;
}
.asset-tree
  :deep(
    .el-tree--highlight-current
      .el-tree-node.is-current
      > .el-tree-node__content
      .node-line
  ) {
  background: #eaf2ff;
}

/* 5) 字体恢复正常权重（避免看起来偏细） */
.asset-tree :deep(.node-left .name-colored) {
  font-weight: 400 !important;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* 6) 如你之前还有这两条，请删除或确保被上面的规则覆盖：
   .asset-tree :deep(.el-tree-node__content){ width:auto !important; }
   .asset-tree :deep(.node-line){ width:max-content; }
*/
/* 取消 el-dialog footer 默认右对齐 */
/* 取消 el-dialog footer 默认右对齐 */

/* 建议加在当前组件的 <style scoped> 里 */
/* 放到 <style scoped> */
.op-grid {
  display: grid;
  grid-template-columns: repeat(3, 22px); /* 一行3个 */
  grid-auto-rows: 22px;
  gap: 6px;
  justify-content: center;
}

.op-cell {
  width: 22px;
  height: 22px;
  display: grid;
  place-items: center; /* 让内容在方块里水平垂直居中 */
}

/* 关键：让 tooltip 的触发器占满方块，去掉基线影响 */
:deep(.op-cell .el-tooltip__trigger) {
  display: flex;
  width: 100%;
  height: 100%;
  align-items: center;
  justify-content: center;
}

.op-btn {
  width: 100%;
  height: 100%;
  padding: 0 !important;
  min-width: 0;
  line-height: 1; /* 去掉基线高度差 */
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* 统一图标尺寸与外边距 */
:deep(.op-btn .el-icon) {
  margin: 0 !important;
  font-size: 16px;
}

/* 每个单元格里把多条备件堆成多行，并保证行高一致 */
.stack {
  display: grid;
  gap: 6px;
}
.stack .line {
  display: flex;
  align-items: center;
  min-height: 24px;
}
.num-col .line {
  justify-content: center;
} /* 数字列水平居中 */
.one-line {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* .stack{ display: grid; gap: 4px; }
.stack .line{ line-height: 20px; }         
.num-col .line{ text-align: center; }
.one-line{ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } */

.spares-grid {
  display: grid;
  gap: 6px;
}
.sp-head,
.sp-row {
  display: grid;
  grid-template-columns: 1fr 90px 100px;
  column-gap: 8px;
  align-items: center;
}
.sp-head {
  font-size: 12px;
  color: #909399;
}
.sp-row .name {
  line-height: 20px;
}
.sp-row .num {
  text-align: right;
}
.one-line {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 内容区：即有滚动也不挤窄 */
:deep(.sp-b) {
  max-height: 160px;
  overflow-y: auto;
  overflow-x: hidden;
  scrollbar-gutter: stable; /* ✅ 关键：预留滚动条宽度 */
}

/* 其余保持原来的三列网格 */
:deep(.sp-h),
:deep(.sp-r) {
  display: grid;
  grid-template-columns: minmax(100px, 1fr) 90px 90px;
  column-gap: 12px;
  align-items: center;
}
/* 表头右侧留出与滚动条同宽的空隙（兼容方案） */
:deep(.sp-h) {
  padding-right: var(--sbw, 0px);
}

/* 吸底条外层：不抢布局 */
.pagination-affix-wrap {
  /* 不需要额外样式 */
}

/* 吸附在底部时的外观 */
.pagination-affix {
  background: #fff;
  border-top: 1px solid #ebeef5;
  padding: 8px 12px;
  box-shadow: 0 -2px 6px rgba(0, 0, 0, 0.04);
  z-index: 20; /* 盖住表格滚动条 */
}

/* 给页面内容留出高度，避免最后一行被分页挡住 */
.app-container {
  padding-bottom: 64px; /* 视你的分页高度微调 */
}

/* 5 列：名称 / 编码 / 可用 / 总数 / 使用；全部左对齐 */
.spare-grid{
  --name-col: minmax(160px, 1.5fr);
  --code-col: minmax(140px, 1.2fr);
  --num-col: 96px;

  display: grid;
  grid-template-columns: var(--name-col) var(--code-col) var(--num-col) var(--num-col) var(--num-col);
  gap: 8px 12px;
  align-items: center;

  /* 长列表时可滚动（可按需删掉） */
  max-height: 260px;
  overflow: auto;
  padding-right: 4px;
}

.spare-grid .head{
  font-size: 12px;
  color: #909399;
  font-weight: 600;
  padding-bottom: 4px;
  border-bottom: 1px dashed #eaecef;
  text-align: left;
}

.spare-grid .cell{
  text-align: left;
  padding: 6px 0;
  border-bottom: 1px dashed #f2f3f5;  /* 行与行之间更清楚 */
}
.spare-grid .cell.name{ font-weight: 600; }
.spare-grid .truncate{
  display: inline-block;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* 编码胶囊：更易读 */
.code-pill{
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
  background: #f5f7fa !important;
  border-color: #e4e7ed !important;
  border-radius: 6px !important;
  height: 22px;
  display: inline-flex;
  align-items: center;
}

/* 数字徽章：等宽数字 + 语义色 */
.badge{
  display: inline-block;
  padding: 0 6px;
  line-height: 20px;
  height: 20px;
  border-radius: 6px;
  border: 1px solid #dcdfe6;
  background: #f6f7fb;
  font-size: 12px;
  font-variant-numeric: tabular-nums; /* 等宽数字，列更齐 */
}
.badge.info{ background: #ecf5ff; border-color: #d9ecff; }   /* 可用 */
.badge.warn{ background: #fff7e6; border-color: #ffe3bf; }   /* 使用 */



.cas-opt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
}
.cas-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cas-badge {
  flex: 0 0 auto;
}

/* 选择框内 tag 的小样式（可选） */
.sel-tag {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.sel-type {
  color: #94a3b8;
  font-size: 12px;
}
</style>
